diff --git a/internal/api/client/admin/admin.go b/internal/api/client/admin/admin.go index b33813a7d..2432cce0f 100644 --- a/internal/api/client/admin/admin.go +++ b/internal/api/client/admin/admin.go @@ -29,10 +29,12 @@ import ( ) const ( - // BasePath is the base API path for this module + // BasePath is the base API path for this module. BasePath = "/api/v1/admin" - // EmojiPath is used for posting/deleting custom emojis + // EmojiPath is used for posting/deleting custom emojis. EmojiPath = BasePath + "/custom_emojis" + // DomainBlocksPath is used for posting domain blocks. + DomainBlocksPath = BasePath + "/domain_blocks" ) // Module implements the ClientAPIModule interface for admin-related actions (reports, emojis, etc) @@ -54,5 +56,6 @@ func New(config *config.Config, processor processing.Processor, log *logrus.Logg // Route attaches all routes from this module to the given router func (m *Module) Route(r router.Router) error { r.AttachHandler(http.MethodPost, EmojiPath, m.emojiCreatePOSTHandler) + r.AttachHandler(http.MethodPost, DomainBlocksPath, m.DomainBlocksPOSTHandler) return nil } diff --git a/internal/api/client/admin/domainblock.go b/internal/api/client/admin/domainblock.go new file mode 100644 index 000000000..651a04116 --- /dev/null +++ b/internal/api/client/admin/domainblock.go @@ -0,0 +1,59 @@ +package admin + +import ( + "fmt" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/superseriousbusiness/gotosocial/internal/api/model" + "github.com/superseriousbusiness/gotosocial/internal/oauth" +) + +func (m *Module) DomainBlocksPOSTHandler(c *gin.Context) { + l := m.log.WithFields(logrus.Fields{ + "func": "DomainBlocksPOSTHandler", + "request_uri": c.Request.RequestURI, + "user_agent": c.Request.UserAgent(), + "origin_ip": c.ClientIP(), + }) + + // make sure we're authed with an admin account + authed, err := oauth.Authed(c, true, true, true, true) + if err != nil { + l.Debugf("couldn't auth: %s", err) + c.JSON(http.StatusForbidden, gin.H{"error": err.Error()}) + return + } + if !authed.User.Admin { + l.Debugf("user %s not an admin", authed.User.ID) + c.JSON(http.StatusForbidden, gin.H{"error": "not an admin"}) + return + } + + // extract the media create form from the request context + l.Tracef("parsing request form: %+v", c.Request.Form) + form := &model.DomainBlockCreateRequest{} + if err := c.ShouldBind(form); err != nil { + l.Debugf("error parsing form %+v: %s", c.Request.Form, err) + c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("could not parse form: %s", err)}) + return + } + + // Give the fields on the request form a first pass to make sure the request is superficially valid. + l.Tracef("validating form %+v", form) + if err := validateCreateDomainBlock(form); err != nil { + l.Debugf("error validating form: %s", err) + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + domainBlock, err := m.processor.AdminDomainBlockCreate(authed, form) + if err != nil { + l.Debugf("error creating domain block: %s", err) + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, domainBlock) +} diff --git a/internal/api/model/domainblock.go b/internal/api/model/domainblock.go new file mode 100644 index 000000000..837d16d21 --- /dev/null +++ b/internal/api/model/domainblock.go @@ -0,0 +1,42 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + 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 . +*/ + +package model + +// DomainBlock represents a block on one domain +type DomainBlock struct { + Domain string `json:"domain"` + Obfuscate bool `json:"obfuscate"` + PrivateComment string `json:"private_comment"` + PublicComment string `json:"public_comment"` + SubscriptionID string `json:"subscription_id"` + CreatedBy string `json:"created_by"` + CreatedAt string `json:"created_at"` +} + +// DomainBlockCreateRequest is the form submitted as a POST to /api/v1/admin/domain_blocks to create a new block. +type DomainBlockCreateRequest struct { + // hostname/domain to block + Domain string `form:"domain" json:"domain" xml:"domain" validation:"required"` + // whether the domain should be obfuscated when being displayed publicly + Obfuscate bool `form:"obfuscate" json:"obfuscate" xml:"obfuscate"` + // private comment for other admins on why the domain was blocked + PrivateComment string `form:"private_comment" json:"private_comment" xml:"private_comment"` + // public comment on the reason for the domain block + PublicComment string `form:"public_comment" json:"public_comment" xml:"public_comment"` +} diff --git a/internal/gtsmodel/domainblock.go b/internal/gtsmodel/domainblock.go index f5c96d832..b7e66ce55 100644 --- a/internal/gtsmodel/domainblock.go +++ b/internal/gtsmodel/domainblock.go @@ -20,13 +20,11 @@ package gtsmodel import "time" -// DomainBlock represents a federation block against a particular domain, of varying severity. +// DomainBlock represents a federation block against a particular domain type DomainBlock struct { // ID of this block in the database ID string `pg:"type:CHAR(26),pk,notnull,unique"` - // Domain to block. If ANY PART of the candidate domain contains this string, it will be blocked. - // For example: 'example.org' also blocks 'gts.example.org'. '.com' blocks *any* '.com' domains. - // TODO: implement wildcards here + // blocked domain Domain string `pg:",notnull"` // When was this block created CreatedAt time.Time `pg:"type:timestamp,notnull,default:now()"` @@ -34,14 +32,12 @@ type DomainBlock struct { UpdatedAt time.Time `pg:"type:timestamp,notnull,default:now()"` // Account ID of the creator of this block CreatedByAccountID string `pg:"type:CHAR(26),notnull"` - // TODO: define this - Severity int - // Reject media from this domain? - RejectMedia bool - // Reject reports from this domain? - RejectReports bool // Private comment on this block, viewable to admins PrivateComment string // Public comment on this block, viewable (optionally) by everyone PublicComment string + // whether the domain name should appear obfuscated when displaying it publicly + Obfuscate bool + // if this block was created through a subscription, what's the subscription ID? + SubscriptionID string `pg:"type:CHAR(26)"` }