diff options
Diffstat (limited to 'internal')
21 files changed, 581 insertions, 104 deletions
diff --git a/internal/api/client/admin/admin.go b/internal/api/client/admin/admin.go index a5a16f35f..01a5796ae 100644 --- a/internal/api/client/admin/admin.go +++ b/internal/api/client/admin/admin.go @@ -102,12 +102,14 @@ func (m *Module) Route(attachHandler func(method string, path string, f ...gin.H attachHandler(http.MethodPost, DomainBlocksPath, m.DomainBlocksPOSTHandler) attachHandler(http.MethodGet, DomainBlocksPath, m.DomainBlocksGETHandler) attachHandler(http.MethodGet, DomainBlocksPathWithID, m.DomainBlockGETHandler) + attachHandler(http.MethodPut, DomainBlocksPathWithID, m.DomainBlockUpdatePUTHandler) 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.MethodPut, DomainAllowsPathWithID, m.DomainAllowUpdatePUTHandler) attachHandler(http.MethodDelete, DomainAllowsPathWithID, m.DomainAllowDELETEHandler) // domain permission draft stuff diff --git a/internal/api/client/admin/domainallowupdate.go b/internal/api/client/admin/domainallowupdate.go new file mode 100644 index 000000000..02edfdfef --- /dev/null +++ b/internal/api/client/admin/domainallowupdate.go @@ -0,0 +1,91 @@ +// 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" +) + +// DomainAllowUpdatePUTHandler swagger:operation PUT /api/v1/admin/domain_allows/{id} domainAllowUpdate +// +// Update a single domain allow. +// +// --- +// tags: +// - admin +// +// consumes: +// - multipart/form-data +// +// produces: +// - application/json +// +// parameters: +// - +// name: id +// type: string +// description: The id of the domain allow. +// in: path +// required: true +// - +// name: obfuscate +// in: formData +// description: >- +// Obfuscate the name of the domain when serving it publicly. +// Eg., `example.org` becomes something like `ex***e.org`. +// 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. +// 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. +// type: string +// +// security: +// - OAuth2 Bearer: +// - admin:write:domain_allows +// +// responses: +// '200': +// description: The updated 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) DomainAllowUpdatePUTHandler(c *gin.Context) { + m.updateDomainPermission(c, gtsmodel.DomainPermissionAllow) +} diff --git a/internal/api/client/admin/domainblockupdate.go b/internal/api/client/admin/domainblockupdate.go new file mode 100644 index 000000000..0fbe72aa8 --- /dev/null +++ b/internal/api/client/admin/domainblockupdate.go @@ -0,0 +1,91 @@ +// 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" +) + +// DomainBlockUpdatePUTHandler swagger:operation PUT /api/v1/admin/domain_blocks/{id} domainBlockUpdate +// +// Update a single domain block. +// +// --- +// tags: +// - admin +// +// consumes: +// - multipart/form-data +// +// produces: +// - application/json +// +// parameters: +// - +// name: id +// type: string +// description: The id of the domain block. +// in: path +// required: true +// - +// name: obfuscate +// in: formData +// description: >- +// Obfuscate the name of the domain when serving it publicly. +// Eg., `example.org` becomes something like `ex***e.org`. +// type: boolean +// - +// name: public_comment +// in: formData +// description: >- +// Public comment about this domain block. +// This will be displayed alongside the domain block if you choose to share blocks. +// type: string +// - +// name: private_comment +// in: formData +// description: >- +// Private comment about this domain block. Will only be shown to other admins, so this +// is a useful way of internally keeping track of why a certain domain ended up blocked. +// type: string +// +// security: +// - OAuth2 Bearer: +// - admin:write:domain_blocks +// +// responses: +// '200': +// description: The updated domain block. +// 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) DomainBlockUpdatePUTHandler(c *gin.Context) { + m.updateDomainPermission(c, gtsmodel.DomainPermissionBlock) +} diff --git a/internal/api/client/admin/domainpermission.go b/internal/api/client/admin/domainpermission.go index c64c90eb2..91b95334b 100644 --- a/internal/api/client/admin/domainpermission.go +++ b/internal/api/client/admin/domainpermission.go @@ -29,6 +29,7 @@ import ( 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/util" ) type singleDomainPermCreate func( @@ -112,7 +113,7 @@ func (m *Module) createDomainPermissions( if importing && form.Domains.Size == 0 { err = errors.New("import was specified but list of domains is empty") } else if !importing && form.Domain == "" { - err = errors.New("empty domain provided") + err = errors.New("no domain provided") } if err != nil { @@ -122,14 +123,14 @@ func (m *Module) createDomainPermissions( if !importing { // Single domain permission creation. - domainBlock, _, errWithCode := single( + perm, _, errWithCode := single( c.Request.Context(), permType, authed.Account, form.Domain, - form.Obfuscate, - form.PublicComment, - form.PrivateComment, + util.PtrOrZero(form.Obfuscate), + util.PtrOrZero(form.PublicComment), + util.PtrOrZero(form.PrivateComment), "", // No sub ID for single perm creation. ) @@ -138,7 +139,7 @@ func (m *Module) createDomainPermissions( return } - apiutil.JSON(c, http.StatusOK, domainBlock) + apiutil.JSON(c, http.StatusOK, perm) return } @@ -177,6 +178,82 @@ func (m *Module) createDomainPermissions( apiutil.JSON(c, http.StatusOK, domainPerms) } +func (m *Module) updateDomainPermission( + c *gin.Context, + permType gtsmodel.DomainPermissionType, +) { + // Scope differs based on permType. + var requireScope apiutil.Scope + if permType == gtsmodel.DomainPermissionBlock { + requireScope = apiutil.ScopeAdminWriteDomainBlocks + } else { + requireScope = apiutil.ScopeAdminWriteDomainAllows + } + + authed, errWithCode := apiutil.TokenAuth(c, + true, true, true, true, + requireScope, + ) + if errWithCode != nil { + apiutil.ErrorHandler(c, errWithCode, 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 + } + + permID, errWithCode := apiutil.ParseID(c.Param(apiutil.IDKey)) + 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 form.Obfuscate == nil && + form.PrivateComment == nil && + form.PublicComment == nil { + const errText = "empty form submitted" + errWithCode := gtserror.NewErrorBadRequest(errors.New(errText), errText) + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) + return + } + + perm, errWithCode := m.processor.Admin().DomainPermissionUpdate( + c.Request.Context(), + permType, + permID, + form.Obfuscate, + form.PublicComment, + form.PrivateComment, + nil, // Can't update perm sub ID this way yet. + ) + if errWithCode != nil { + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) + return + } + + apiutil.JSON(c, http.StatusOK, perm) +} + // deleteDomainPermission deletes a single domain permission (block or allow). func (m *Module) deleteDomainPermission( c *gin.Context, diff --git a/internal/api/client/admin/domainpermissiondraftcreate.go b/internal/api/client/admin/domainpermissiondraftcreate.go index b8d3085e9..e7fcd2c40 100644 --- a/internal/api/client/admin/domainpermissiondraftcreate.go +++ b/internal/api/client/admin/domainpermissiondraftcreate.go @@ -26,6 +26,7 @@ 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/util" ) // DomainPermissionDraftsPOSTHandler swagger:operation POST /api/v1/admin/domain_permission_drafts domainPermissionDraftCreate @@ -148,9 +149,9 @@ func (m *Module) DomainPermissionDraftsPOSTHandler(c *gin.Context) { authed.Account, form.Domain, permType, - form.Obfuscate, - form.PublicComment, - form.PrivateComment, + util.PtrOrZero(form.Obfuscate), + util.PtrOrZero(form.PublicComment), + util.PtrOrZero(form.PrivateComment), ) if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) diff --git a/internal/api/client/admin/domainpermissionsubscriptiontest_test.go b/internal/api/client/admin/domainpermissionsubscriptiontest_test.go index c03b950a9..4ac366520 100644 --- a/internal/api/client/admin/domainpermissionsubscriptiontest_test.go +++ b/internal/api/client/admin/domainpermissionsubscriptiontest_test.go @@ -97,14 +97,21 @@ func (suite *DomainPermissionSubscriptionTestTestSuite) TestDomainPermissionSubs suite.Equal(`[ { "domain": "bumfaces.net", - "public_comment": "big jerks" + "public_comment": "big jerks", + "obfuscate": false, + "private_comment": "" }, { "domain": "peepee.poopoo", - "public_comment": "harassment" + "public_comment": "harassment", + "obfuscate": false, + "private_comment": "" }, { - "domain": "nothanks.com" + "domain": "nothanks.com", + "public_comment": "", + "obfuscate": false, + "private_comment": "" } ]`, dst.String()) @@ -177,13 +184,22 @@ func (suite *DomainPermissionSubscriptionTestTestSuite) TestDomainPermissionSubs // Ensure expected. suite.Equal(`[ { - "domain": "bumfaces.net" + "domain": "bumfaces.net", + "public_comment": "", + "obfuscate": false, + "private_comment": "" }, { - "domain": "peepee.poopoo" + "domain": "peepee.poopoo", + "public_comment": "", + "obfuscate": false, + "private_comment": "" }, { - "domain": "nothanks.com" + "domain": "nothanks.com", + "public_comment": "", + "obfuscate": false, + "private_comment": "" } ]`, dst.String()) diff --git a/internal/api/client/instance/instancepeersget_test.go b/internal/api/client/instance/instancepeersget_test.go index a2c81cc4e..2421205f7 100644 --- a/internal/api/client/instance/instancepeersget_test.go +++ b/internal/api/client/instance/instancepeersget_test.go @@ -136,7 +136,7 @@ func (suite *InstancePeersGetTestSuite) TestInstancePeersGetOnlySuspended() { { "domain": "replyguys.com", "suspended_at": "2020-05-13T13:29:12.000Z", - "public_comment": "reply-guying to tech posts" + "comment": "reply-guying to tech posts" } ]`, dst.String()) } @@ -186,7 +186,7 @@ func (suite *InstancePeersGetTestSuite) TestInstancePeersGetOnlySuspendedAuthori { "domain": "replyguys.com", "suspended_at": "2020-05-13T13:29:12.000Z", - "public_comment": "reply-guying to tech posts" + "comment": "reply-guying to tech posts" } ]`, dst.String()) } @@ -219,7 +219,7 @@ func (suite *InstancePeersGetTestSuite) TestInstancePeersGetAll() { { "domain": "replyguys.com", "suspended_at": "2020-05-13T13:29:12.000Z", - "public_comment": "reply-guying to tech posts" + "comment": "reply-guying to tech posts" } ]`, dst.String()) } @@ -263,12 +263,12 @@ func (suite *InstancePeersGetTestSuite) TestInstancePeersGetAllWithObfuscated() { "domain": "o*g.*u**.t**.*or*t.*r**ev**", "suspended_at": "2021-06-09T10:34:55.000Z", - "public_comment": "just absolutely the worst, wowza" + "comment": "just absolutely the worst, wowza" }, { "domain": "replyguys.com", "suspended_at": "2020-05-13T13:29:12.000Z", - "public_comment": "reply-guying to tech posts" + "comment": "reply-guying to tech posts" } ]`, dst.String()) } diff --git a/internal/api/model/domain.go b/internal/api/model/domain.go index 94a190f63..8d94321d0 100644 --- a/internal/api/model/domain.go +++ b/internal/api/model/domain.go @@ -33,8 +33,13 @@ type Domain struct { // example: 2021-07-30T09:20:25+00:00 SilencedAt string `json:"silenced_at,omitempty"` // If the domain is blocked, what's the publicly-stated reason for the block. + // Alternative to `public_comment` to be used when serializing/deserializing via /api/v1/instance. // example: they smell - PublicComment string `form:"public_comment" json:"public_comment,omitempty"` + Comment *string `form:"comment" json:"comment,omitempty"` + // If the domain is blocked, what's the publicly-stated reason for the block. + // Alternative to `comment` to be used when serializing/deserializing NOT via /api/v1/instance. + // example: they smell + PublicComment *string `form:"public_comment" json:"public_comment,omitempty"` } // DomainPermission represents a permission applied to one domain (explicit block/allow). @@ -48,10 +53,10 @@ type DomainPermission struct { ID string `json:"id,omitempty"` // Obfuscate the domain name when serving this domain permission entry publicly. // example: false - Obfuscate bool `json:"obfuscate,omitempty"` + Obfuscate *bool `json:"obfuscate,omitempty"` // Private comment for this permission entry, visible to this instance's admins only. // example: they are poopoo - PrivateComment string `json:"private_comment,omitempty"` + PrivateComment *string `json:"private_comment,omitempty"` // If applicable, the ID of the subscription that caused this domain permission entry to be created. // example: 01FBW25TF5J67JW3HFHZCSD23K SubscriptionID string `json:"subscription_id,omitempty"` @@ -80,14 +85,14 @@ type DomainPermissionRequest struct { // Obfuscate the domain name when displaying this permission entry publicly. // Ie., instead of 'example.org' show something like 'e**mpl*.or*'. // example: false - Obfuscate bool `form:"obfuscate" json:"obfuscate"` + Obfuscate *bool `form:"obfuscate" json:"obfuscate"` // Private comment for other admins on why this permission entry was created. // example: don't like 'em!!!! - PrivateComment string `form:"private_comment" json:"private_comment"` + PrivateComment *string `form:"private_comment" json:"private_comment"` // Public comment on why this permission entry was created. // Will be visible to requesters at /api/v1/instance/peers if this endpoint is exposed. // example: foss dorks 😫 - PublicComment string `form:"public_comment" json:"public_comment"` + PublicComment *string `form:"public_comment" json:"public_comment"` // Permission type to create (only applies to domain permission drafts, not explicit blocks and allows). PermissionType string `form:"permission_type" json:"permission_type"` } diff --git a/internal/db/bundb/domain.go b/internal/db/bundb/domain.go index 925387bd9..23b9abc74 100644 --- a/internal/db/bundb/domain.go +++ b/internal/db/bundb/domain.go @@ -36,7 +36,7 @@ type domainDB struct { state *state.State } -func (d *domainDB) CreateDomainAllow(ctx context.Context, allow *gtsmodel.DomainAllow) (err error) { +func (d *domainDB) PutDomainAllow(ctx context.Context, allow *gtsmodel.DomainAllow) (err error) { // Normalize the domain as punycode, note the extra // validation step for domain name write operations. allow.Domain, err = util.PunifySafely(allow.Domain) @@ -162,7 +162,7 @@ func (d *domainDB) DeleteDomainAllow(ctx context.Context, domain string) error { return nil } -func (d *domainDB) CreateDomainBlock(ctx context.Context, block *gtsmodel.DomainBlock) error { +func (d *domainDB) PutDomainBlock(ctx context.Context, block *gtsmodel.DomainBlock) error { var err error // Normalize the domain as punycode, note the extra diff --git a/internal/db/bundb/domain_test.go b/internal/db/bundb/domain_test.go index 8164259e8..a56f469c4 100644 --- a/internal/db/bundb/domain_test.go +++ b/internal/db/bundb/domain_test.go @@ -46,7 +46,7 @@ func (suite *DomainTestSuite) TestIsDomainBlocked() { suite.NoError(err) suite.False(blocked) - err = suite.db.CreateDomainBlock(ctx, domainBlock) + err = suite.db.PutDomainBlock(ctx, domainBlock) suite.NoError(err) // domain block now exists @@ -75,7 +75,7 @@ func (suite *DomainTestSuite) TestIsDomainBlockedWithAllow() { suite.False(blocked) // Block this domain. - if err := suite.db.CreateDomainBlock(ctx, domainBlock); err != nil { + if err := suite.db.PutDomainBlock(ctx, domainBlock); err != nil { suite.FailNow(err.Error()) } @@ -96,7 +96,7 @@ func (suite *DomainTestSuite) TestIsDomainBlockedWithAllow() { CreatedByAccount: suite.testAccounts["admin_account"], } - if err := suite.db.CreateDomainAllow(ctx, domainAllow); err != nil { + if err := suite.db.PutDomainAllow(ctx, domainAllow); err != nil { suite.FailNow(err.Error()) } @@ -124,7 +124,7 @@ func (suite *DomainTestSuite) TestIsDomainBlockedWildcard() { suite.NoError(err) suite.False(blocked) - err = suite.db.CreateDomainBlock(ctx, domainBlock) + err = suite.db.PutDomainBlock(ctx, domainBlock) suite.NoError(err) // Start with the base block domain @@ -164,7 +164,7 @@ func (suite *DomainTestSuite) TestIsDomainBlockedNonASCII() { suite.NoError(err) suite.False(blocked) - err = suite.db.CreateDomainBlock(ctx, domainBlock) + err = suite.db.PutDomainBlock(ctx, domainBlock) suite.NoError(err) // domain block now exists @@ -200,7 +200,7 @@ func (suite *DomainTestSuite) TestIsDomainBlockedNonASCII2() { suite.NoError(err) suite.False(blocked) - err = suite.db.CreateDomainBlock(ctx, domainBlock) + err = suite.db.PutDomainBlock(ctx, domainBlock) suite.NoError(err) // domain block now exists @@ -232,7 +232,7 @@ func (suite *DomainTestSuite) TestIsOtherDomainBlockedWildcardAndExplicit() { } for _, block := range blocks { - if err := suite.db.CreateDomainBlock(ctx, block); err != nil { + if err := suite.db.PutDomainBlock(ctx, block); err != nil { suite.FailNow(err.Error()) } } diff --git a/internal/db/bundb/domainpermissionsubscription_test.go b/internal/db/bundb/domainpermissionsubscription_test.go index 732befbff..7a5cf8685 100644 --- a/internal/db/bundb/domainpermissionsubscription_test.go +++ b/internal/db/bundb/domainpermissionsubscription_test.go @@ -80,7 +80,7 @@ func (suite *DomainPermissionSubscriptionTestSuite) TestCount() { // Whack the perms in the db. for _, perm := range perms { - if err := suite.state.DB.CreateDomainBlock(ctx, perm); err != nil { + if err := suite.state.DB.PutDomainBlock(ctx, perm); err != nil { suite.FailNow(err.Error()) } } diff --git a/internal/db/domain.go b/internal/db/domain.go index 643538e7e..95a2f0755 100644 --- a/internal/db/domain.go +++ b/internal/db/domain.go @@ -31,8 +31,8 @@ type Domain interface { Block/allow storage + retrieval functions. */ - // CreateDomainAllow puts the given instance-level domain allow into the database. - CreateDomainAllow(ctx context.Context, allow *gtsmodel.DomainAllow) error + // PutDomainAllow puts the given instance-level domain allow into the database. + PutDomainAllow(ctx context.Context, allow *gtsmodel.DomainAllow) error // GetDomainAllow returns one instance-level domain allow with the given domain, if it exists. GetDomainAllow(ctx context.Context, domain string) (*gtsmodel.DomainAllow, error) @@ -49,8 +49,8 @@ type Domain interface { // DeleteDomainAllow deletes an instance-level domain allow with the given domain, if it exists. DeleteDomainAllow(ctx context.Context, domain string) error - // CreateDomainBlock puts the given instance-level domain block into the database. - CreateDomainBlock(ctx context.Context, block *gtsmodel.DomainBlock) error + // PutDomainBlock puts the given instance-level domain block into the database. + PutDomainBlock(ctx context.Context, block *gtsmodel.DomainBlock) error // GetDomainBlock returns one instance-level domain block with the given domain, if it exists. GetDomainBlock(ctx context.Context, domain string) (*gtsmodel.DomainBlock, error) diff --git a/internal/gtsmodel/domainallow.go b/internal/gtsmodel/domainallow.go index 3a7ca8774..f6aedbbba 100644 --- a/internal/gtsmodel/domainallow.go +++ b/internal/gtsmodel/domainallow.go @@ -26,7 +26,7 @@ type DomainAllow struct { UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated Domain string `bun:",nullzero,notnull"` // domain to allow. Eg. 'whatever.com' CreatedByAccountID string `bun:"type:CHAR(26),nullzero,notnull"` // Account ID of the creator of this allow - CreatedByAccount *Account `bun:"rel:belongs-to"` // Account corresponding to createdByAccountID + CreatedByAccount *Account `bun:"-"` // Account corresponding to createdByAccountID PrivateComment string `bun:""` // Private comment on this allow, viewable to admins PublicComment string `bun:""` // Public comment on this allow, viewable (optionally) by everyone Obfuscate *bool `bun:",nullzero,notnull,default:false"` // whether the domain name should appear obfuscated when displaying it publicly diff --git a/internal/gtsmodel/domainblock.go b/internal/gtsmodel/domainblock.go index 4a0e1c5b7..fb0921c25 100644 --- a/internal/gtsmodel/domainblock.go +++ b/internal/gtsmodel/domainblock.go @@ -26,7 +26,7 @@ type DomainBlock struct { UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated Domain string `bun:",nullzero,notnull"` // domain to block. Eg. 'whatever.com' CreatedByAccountID string `bun:"type:CHAR(26),nullzero,notnull"` // Account ID of the creator of this block - CreatedByAccount *Account `bun:"rel:belongs-to"` // Account corresponding to createdByAccountID + CreatedByAccount *Account `bun:"-"` // Account corresponding to createdByAccountID PrivateComment string `bun:""` // Private comment on this block, viewable to admins PublicComment string `bun:""` // Public comment on this block, viewable (optionally) by everyone Obfuscate *bool `bun:",nullzero,notnull,default:false"` // whether the domain name should appear obfuscated when displaying it publicly diff --git a/internal/processing/admin/domainallow.go b/internal/processing/admin/domainallow.go index 02101ccff..134351ad5 100644 --- a/internal/processing/admin/domainallow.go +++ b/internal/processing/admin/domainallow.go @@ -60,7 +60,7 @@ func (p *Processor) createDomainAllow( } // Insert the new allow into the database. - if err := p.state.DB.CreateDomainAllow(ctx, domainAllow); err != nil { + if err := p.state.DB.PutDomainAllow(ctx, domainAllow); err != nil { err = gtserror.Newf("db error putting domain allow %s: %w", domain, err) return nil, "", gtserror.NewErrorInternalError(err) } @@ -92,6 +92,54 @@ func (p *Processor) createDomainAllow( return apiDomainAllow, action.ID, nil } +func (p *Processor) updateDomainAllow( + ctx context.Context, + domainAllowID string, + obfuscate *bool, + publicComment *string, + privateComment *string, + subscriptionID *string, +) (*apimodel.DomainPermission, gtserror.WithCode) { + domainAllow, err := p.state.DB.GetDomainAllowByID(ctx, domainAllowID) + if err != nil { + if !errors.Is(err, db.ErrNoEntries) { + // Real error. + err = gtserror.Newf("db error getting domain allow: %w", err) + return nil, gtserror.NewErrorInternalError(err) + } + + // There are just no entries for this ID. + err = fmt.Errorf("no domain allow entry exists with ID %s", domainAllowID) + return nil, gtserror.NewErrorNotFound(err, err.Error()) + } + + var columns []string + if obfuscate != nil { + domainAllow.Obfuscate = obfuscate + columns = append(columns, "obfuscate") + } + if publicComment != nil { + domainAllow.PublicComment = *publicComment + columns = append(columns, "public_comment") + } + if privateComment != nil { + domainAllow.PrivateComment = *privateComment + columns = append(columns, "private_comment") + } + if subscriptionID != nil { + domainAllow.SubscriptionID = *subscriptionID + columns = append(columns, "subscription_id") + } + + // Update the domain allow. + if err := p.state.DB.UpdateDomainAllow(ctx, domainAllow, columns...); err != nil { + err = gtserror.Newf("db error updating domain allow: %w", err) + return nil, gtserror.NewErrorInternalError(err) + } + + return p.apiDomainPerm(ctx, domainAllow, false) +} + func (p *Processor) deleteDomainAllow( ctx context.Context, adminAcct *gtsmodel.Account, diff --git a/internal/processing/admin/domainblock.go b/internal/processing/admin/domainblock.go index 249df744c..3dd5a256f 100644 --- a/internal/processing/admin/domainblock.go +++ b/internal/processing/admin/domainblock.go @@ -60,7 +60,7 @@ func (p *Processor) createDomainBlock( } // Insert the new block into the database. - if err := p.state.DB.CreateDomainBlock(ctx, domainBlock); err != nil { + if err := p.state.DB.PutDomainBlock(ctx, domainBlock); err != nil { err = gtserror.Newf("db error putting domain block %s: %w", domain, err) return nil, "", gtserror.NewErrorInternalError(err) } @@ -93,6 +93,54 @@ func (p *Processor) createDomainBlock( return apiDomainBlock, action.ID, nil } +func (p *Processor) updateDomainBlock( + ctx context.Context, + domainBlockID string, + obfuscate *bool, + publicComment *string, + privateComment *string, + subscriptionID *string, +) (*apimodel.DomainPermission, gtserror.WithCode) { + domainBlock, err := p.state.DB.GetDomainBlockByID(ctx, domainBlockID) + if err != nil { + if !errors.Is(err, db.ErrNoEntries) { + // Real error. + err = gtserror.Newf("db error getting domain block: %w", err) + return nil, gtserror.NewErrorInternalError(err) + } + + // There are just no entries for this ID. + err = fmt.Errorf("no domain block entry exists with ID %s", domainBlockID) + return nil, gtserror.NewErrorNotFound(err, err.Error()) + } + + var columns []string + if obfuscate != nil { + domainBlock.Obfuscate = obfuscate + columns = append(columns, "obfuscate") + } + if publicComment != nil { + domainBlock.PublicComment = *publicComment + columns = append(columns, "public_comment") + } + if privateComment != nil { + domainBlock.PrivateComment = *privateComment + columns = append(columns, "private_comment") + } + if subscriptionID != nil { + domainBlock.SubscriptionID = *subscriptionID + columns = append(columns, "subscription_id") + } + + // Update the domain block. + if err := p.state.DB.UpdateDomainBlock(ctx, domainBlock, columns...); err != nil { + err = gtserror.Newf("db error updating domain block: %w", err) + return nil, gtserror.NewErrorInternalError(err) + } + + return p.apiDomainPerm(ctx, domainBlock, false) +} + func (p *Processor) deleteDomainBlock( ctx context.Context, adminAcct *gtsmodel.Account, diff --git a/internal/processing/admin/domainpermission.go b/internal/processing/admin/domainpermission.go index 55800f458..04ee2ab26 100644 --- a/internal/processing/admin/domainpermission.go +++ b/internal/processing/admin/domainpermission.go @@ -18,6 +18,7 @@ package admin import ( + "cmp" "context" "encoding/json" "errors" @@ -29,6 +30,7 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/util" ) // DomainPermissionCreate creates an instance-level permission @@ -84,6 +86,50 @@ func (p *Processor) DomainPermissionCreate( } } +// DomainPermissionUpdate updates a domain permission +// of the given permissionType, with the given ID. +func (p *Processor) DomainPermissionUpdate( + ctx context.Context, + permissionType gtsmodel.DomainPermissionType, + permID string, + obfuscate *bool, + publicComment *string, + privateComment *string, + subscriptionID *string, +) (*apimodel.DomainPermission, gtserror.WithCode) { + switch permissionType { + + // Explicitly block a domain. + case gtsmodel.DomainPermissionBlock: + return p.updateDomainBlock( + ctx, + permID, + obfuscate, + publicComment, + privateComment, + subscriptionID, + ) + + // Explicitly allow a domain. + case gtsmodel.DomainPermissionAllow: + return p.updateDomainAllow( + ctx, + permID, + obfuscate, + publicComment, + privateComment, + subscriptionID, + ) + + // 🎵 Why don't we all strap bombs to our chests, + // and ride our bikes to the next G7 picnic? + // Seems easier with every clock-tick. 🎵 + default: + err := gtserror.Newf("unrecognized permission type %d", permissionType) + return nil, gtserror.NewErrorInternalError(err) + } +} + // DomainPermissionDelete removes one domain block with the given ID, // and processes side effects of removing the block asynchronously. // @@ -153,14 +199,14 @@ func (p *Processor) DomainPermissionsImport( } defer file.Close() - // Parse file as slice of domain blocks. - domainPerms := make([]*apimodel.DomainPermission, 0) - if err := json.NewDecoder(file).Decode(&domainPerms); err != nil { + // Parse file as slice of domain permissions. + apiDomainPerms := make([]*apimodel.DomainPermission, 0) + if err := json.NewDecoder(file).Decode(&apiDomainPerms); err != nil { err = gtserror.Newf("error parsing attachment as domain permissions: %w", err) return nil, gtserror.NewErrorBadRequest(err, err.Error()) } - count := len(domainPerms) + count := len(apiDomainPerms) if count == 0 { err = gtserror.New("error importing domain permissions: 0 entries provided") return nil, gtserror.NewErrorBadRequest(err, err.Error()) @@ -170,50 +216,95 @@ func (p *Processor) DomainPermissionsImport( // between successes and errors so that the caller can // try failed imports again if desired. multiStatusEntries := make([]apimodel.MultiStatusEntry, 0, count) - - for _, domainPerm := range domainPerms { - var ( - domain = domainPerm.Domain.Domain - obfuscate = domainPerm.Obfuscate - publicComment = domainPerm.PublicComment - privateComment = domainPerm.PrivateComment - subscriptionID = "" // No sub ID for imports. - errWithCode gtserror.WithCode + for _, apiDomainPerm := range apiDomainPerms { + multiStatusEntries = append( + multiStatusEntries, + p.importOrUpdateDomainPerm( + ctx, + permissionType, + account, + apiDomainPerm, + ), ) + } + + return apimodel.NewMultiStatus(multiStatusEntries), nil +} + +func (p *Processor) importOrUpdateDomainPerm( + ctx context.Context, + permType gtsmodel.DomainPermissionType, + account *gtsmodel.Account, + apiDomainPerm *apimodel.DomainPermission, +) apimodel.MultiStatusEntry { + var ( + domain = apiDomainPerm.Domain.Domain + obfuscate = apiDomainPerm.Obfuscate + publicComment = cmp.Or(apiDomainPerm.PublicComment, apiDomainPerm.Comment) + privateComment = apiDomainPerm.PrivateComment + subscriptionID = "" // No sub ID for imports. + ) + + // Check if this domain + // perm already exists. + var ( + domainPerm gtsmodel.DomainPermission + err error + ) + if permType == gtsmodel.DomainPermissionBlock { + domainPerm, err = p.state.DB.GetDomainBlock(ctx, domain) + } else { + domainPerm, err = p.state.DB.GetDomainAllow(ctx, domain) + } - domainPerm, _, errWithCode = p.DomainPermissionCreate( + if err != nil && !errors.Is(err, db.ErrNoEntries) { + // Real db error. + return apimodel.MultiStatusEntry{ + Resource: domain, + Message: "db error checking for existence of domain permission", + Status: http.StatusInternalServerError, + } + } + + var errWithCode gtserror.WithCode + if domainPerm != nil { + // Permission already exists, update it. + apiDomainPerm, errWithCode = p.DomainPermissionUpdate( ctx, - permissionType, - account, - domain, + permType, + domainPerm.GetID(), obfuscate, publicComment, privateComment, + nil, + ) + } else { + // Permission didn't exist yet, create it. + apiDomainPerm, _, errWithCode = p.DomainPermissionCreate( + ctx, + permType, + account, + domain, + util.PtrOrZero(obfuscate), + util.PtrOrZero(publicComment), + util.PtrOrZero(privateComment), subscriptionID, ) + } - var entry *apimodel.MultiStatusEntry - - if errWithCode != nil { - entry = &apimodel.MultiStatusEntry{ - // Use the failed domain entry as the resource value. - Resource: domain, - Message: errWithCode.Safe(), - Status: errWithCode.Code(), - } - } else { - entry = &apimodel.MultiStatusEntry{ - // Use successfully created API model domain block as the resource value. - Resource: domainPerm, - Message: http.StatusText(http.StatusOK), - Status: http.StatusOK, - } + if errWithCode != nil { + return apimodel.MultiStatusEntry{ + Resource: domain, + Message: errWithCode.Safe(), + Status: errWithCode.Code(), } - - multiStatusEntries = append(multiStatusEntries, *entry) } - return apimodel.NewMultiStatus(multiStatusEntries), nil + return apimodel.MultiStatusEntry{ + Resource: apiDomainPerm, + Message: http.StatusText(http.StatusOK), + Status: http.StatusOK, + } } // DomainPermissionsGet returns all existing domain diff --git a/internal/processing/instance.go b/internal/processing/instance.go index 4cbbb742a..e723c751e 100644 --- a/internal/processing/instance.go +++ b/internal/processing/instance.go @@ -106,9 +106,9 @@ func (p *Processor) InstancePeersGet(ctx context.Context, includeSuspended bool, } domains = append(domains, &apimodel.Domain{ - Domain: d, - SuspendedAt: util.FormatISO8601(domainBlock.CreatedAt), - PublicComment: domainBlock.PublicComment, + Domain: d, + SuspendedAt: util.FormatISO8601(domainBlock.CreatedAt), + Comment: &domainBlock.PublicComment, }) } } diff --git a/internal/subscriptions/domainperms.go b/internal/subscriptions/domainperms.go index c9f569f94..8da9064f6 100644 --- a/internal/subscriptions/domainperms.go +++ b/internal/subscriptions/domainperms.go @@ -438,7 +438,7 @@ func (s *Subscriptions) processDomainPermission( Obfuscate: wantedPerm.GetObfuscate(), SubscriptionID: permSub.ID, } - insertF = func() error { return s.state.DB.CreateDomainBlock(ctx, domainBlock) } + insertF = func() error { return s.state.DB.PutDomainBlock(ctx, domainBlock) } action = >smodel.AdminAction{ ID: id.NewULID(), @@ -461,7 +461,7 @@ func (s *Subscriptions) processDomainPermission( Obfuscate: wantedPerm.GetObfuscate(), SubscriptionID: permSub.ID, } - insertF = func() error { return s.state.DB.CreateDomainAllow(ctx, domainAllow) } + insertF = func() error { return s.state.DB.PutDomainAllow(ctx, domainAllow) } action = >smodel.AdminAction{ ID: id.NewULID(), @@ -564,13 +564,13 @@ func permsFromCSV( for i, columnHeader := range columnHeaders { // Remove leading # if present. - normal := strings.TrimLeft(columnHeader, "#") + columnHeader = strings.TrimLeft(columnHeader, "#") // Find index of each column header we // care about, ensuring no duplicates. - switch normal { + switch { - case "domain": + case columnHeader == "domain": if domainI != nil { body.Close() err := gtserror.NewfAt(3, "duplicate domain column header in csv: %+v", columnHeaders) @@ -578,7 +578,7 @@ func permsFromCSV( } domainI = &i - case "severity": + case columnHeader == "severity": if severityI != nil { body.Close() err := gtserror.NewfAt(3, "duplicate severity column header in csv: %+v", columnHeaders) @@ -586,15 +586,15 @@ func permsFromCSV( } severityI = &i - case "public_comment": + case columnHeader == "public_comment" || columnHeader == "comment": if publicCommentI != nil { body.Close() - err := gtserror.NewfAt(3, "duplicate public_comment column header in csv: %+v", columnHeaders) + err := gtserror.NewfAt(3, "duplicate public_comment or comment column header in csv: %+v", columnHeaders) return nil, err } publicCommentI = &i - case "obfuscate": + case columnHeader == "obfuscate": if obfuscateI != nil { body.Close() err := gtserror.NewfAt(3, "duplicate obfuscate column header in csv: %+v", columnHeaders) @@ -674,15 +674,15 @@ func permsFromCSV( perm.SetPublicComment(record[*publicCommentI]) } + var obfuscate bool if obfuscateI != nil { - obfuscate, err := strconv.ParseBool(record[*obfuscateI]) + obfuscate, err = strconv.ParseBool(record[*obfuscateI]) if err != nil { l.Warnf("couldn't parse obfuscate field of record: %+v", record) continue } - - perm.SetObfuscate(&obfuscate) } + perm.SetObfuscate(&obfuscate) // We're done. perms = append(perms, perm) @@ -742,8 +742,9 @@ func permsFromJSON( } // Set remaining fields. - perm.SetPublicComment(apiPerm.PublicComment) - perm.SetObfuscate(&apiPerm.Obfuscate) + publicComment := cmp.Or(apiPerm.PublicComment, apiPerm.Comment) + perm.SetPublicComment(util.PtrOrZero(publicComment)) + perm.SetObfuscate(util.Ptr(util.PtrOrZero(apiPerm.Obfuscate))) // We're done. perms = append(perms, perm) @@ -792,9 +793,15 @@ func permsFromPlain( var perm gtsmodel.DomainPermission switch permType { case gtsmodel.DomainPermissionBlock: - perm = >smodel.DomainBlock{Domain: domain} + perm = >smodel.DomainBlock{ + Domain: domain, + Obfuscate: util.Ptr(false), + } case gtsmodel.DomainPermissionAllow: - perm = >smodel.DomainAllow{Domain: domain} + perm = >smodel.DomainAllow{ + Domain: domain, + Obfuscate: util.Ptr(false), + } } // We're done. diff --git a/internal/subscriptions/subscriptions_test.go b/internal/subscriptions/subscriptions_test.go index 133db4b7c..4441d8c15 100644 --- a/internal/subscriptions/subscriptions_test.go +++ b/internal/subscriptions/subscriptions_test.go @@ -775,7 +775,7 @@ func (suite *SubscriptionsTestSuite) TestAdoption() { existingBlock2, existingBlock3, } { - if err := testStructs.State.DB.CreateDomainBlock( + if err := testStructs.State.DB.PutDomainBlock( ctx, block, ); err != nil { suite.FailNow(err.Error()) @@ -876,7 +876,7 @@ func (suite *SubscriptionsTestSuite) TestDomainAllowsAndBlocks() { } // Store existing allow. - if err := testStructs.State.DB.CreateDomainAllow(ctx, existingAllow); err != nil { + if err := testStructs.State.DB.PutDomainAllow(ctx, existingAllow); err != nil { suite.FailNow(err.Error()) } diff --git a/internal/typeutils/internaltofrontend.go b/internal/typeutils/internaltofrontend.go index b0f5d12fa..62a1ebc1e 100644 --- a/internal/typeutils/internaltofrontend.go +++ b/internal/typeutils/internaltofrontend.go @@ -2182,7 +2182,7 @@ func (c *Converter) DomainPermToAPIDomainPerm( domainPerm := &apimodel.DomainPermission{ Domain: apimodel.Domain{ Domain: domain, - PublicComment: d.GetPublicComment(), + PublicComment: util.Ptr(d.GetPublicComment()), }, } @@ -2193,8 +2193,8 @@ func (c *Converter) DomainPermToAPIDomainPerm( } domainPerm.ID = d.GetID() - domainPerm.Obfuscate = util.PtrOrZero(d.GetObfuscate()) - domainPerm.PrivateComment = d.GetPrivateComment() + domainPerm.Obfuscate = d.GetObfuscate() + domainPerm.PrivateComment = util.Ptr(d.GetPrivateComment()) domainPerm.SubscriptionID = d.GetSubscriptionID() domainPerm.CreatedBy = d.GetCreatedByAccountID() if createdAt := d.GetCreatedAt(); !createdAt.IsZero() { |
