Skip to content

Commit

Permalink
[feature] Admin account actions (#432)
Browse files Browse the repository at this point in the history
* add accountAction to the admin API

* model admin account action

* add admin account action to the processor

* add migration for new AdminAccountActions table

* fix accounts admin path

* Update swagger docs
  • Loading branch information
tsmethurst authored Mar 19, 2022
1 parent 532c4cc commit 55ad6de
Show file tree
Hide file tree
Showing 14 changed files with 649 additions and 2 deletions.
37 changes: 37 additions & 0 deletions docs/api/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2336,6 +2336,43 @@ paths:
summary: Verify a token by returning account details pertaining to it.
tags:
- accounts
/api/v1/admin/accounts/{id}/action:
post:
consumes:
- multipart/form-data
operationId: adminAccountAction
parameters:
- description: ID of the account.
in: path
name: id
required: true
type: string
- description: 'Type of action to be taken. One of: disable, silence, suspend.'
in: formData
name: type
required: true
type: string
- description: Optional text describing why this action was taken.
in: formData
name: text
type: string
produces:
- application/json
responses:
"200":
description: OK
"400":
description: bad request
"401":
description: unauthorized
"403":
description: forbidden
security:
- OAuth2 Bearer:
- admin
summary: Perform an admin action on an account.
tags:
- admin
/api/v1/admin/custom_emojis:
post:
consumes:
Expand Down
126 changes: 126 additions & 0 deletions internal/api/client/admin/accountaction.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/*
GoToSocial
Copyright (C) 2021-2022 GoToSocial Authors [email protected]
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"
"github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/api/model"
"github.com/superseriousbusiness/gotosocial/internal/oauth"
)

// AccountActionPOSTHandler swagger:operation POST /api/v1/admin/accounts/{id}/action adminAccountAction
//
// Perform an admin action on an account.
//
// ---
// tags:
// - admin
//
// consumes:
// - multipart/form-data
//
// produces:
// - application/json
//
// parameters:
// - name: id
// required: true
// in: path
// description: ID of the account.
// type: string
// - name: type
// in: formData
// description: |-
// Type of action to be taken. One of: disable, silence, suspend.
// type: string
// required: true
// - name: text
// in: formData
// description: Optional text describing why this action was taken.
// type: string
//
// security:
// - OAuth2 Bearer:
// - admin
//
// responses:
// '200':
// description: OK
// '400':
// description: bad request
// '401':
// description: unauthorized
// '403':
// description: forbidden
func (m *Module) AccountActionPOSTHandler(c *gin.Context) {
l := logrus.WithFields(logrus.Fields{
"func": "AccountActionPOSTHandler",
"request_uri": c.Request.RequestURI,
"user_agent": c.Request.UserAgent(),
"origin_ip": c.ClientIP(),
})

// make sure we're authed...
authed, err := oauth.Authed(c, true, true, true, true)
if err != nil {
l.Debugf("couldn't auth: %s", err)
c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
return
}

// with an admin account
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 form from the request context
l.Tracef("parsing request form: %+v", c.Request.Form)
form := &model.AdminAccountActionRequest{}
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
}

if form.Type == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "no type specified"})
return
}

targetAcctID := c.Param(IDKey)
if targetAcctID == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "no account id specified"})
return
}
form.TargetAccountID = targetAcctID

if errWithCode := m.processor.AdminAccountAction(c.Request.Context(), authed, form); errWithCode != nil {
l.Debugf("error performing account action: %s", errWithCode.Error())
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
return
}

c.JSON(http.StatusOK, gin.H{"message": "OK"})
}
7 changes: 7 additions & 0 deletions internal/api/client/admin/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ const (
DomainBlocksPath = BasePath + "/domain_blocks"
// DomainBlocksPathWithID is used for interacting with a single domain block.
DomainBlocksPathWithID = DomainBlocksPath + "/:" + IDKey
// AccountsPath is used for listing + acting on accounts.
AccountsPath = BasePath + "/accounts"
// AccountsPathWithID is used for interacting with a single account.
AccountsPathWithID = AccountsPath + "/:" + IDKey
// AccountsActionPath is used for taking action on a single account.
AccountsActionPath = AccountsPathWithID + "/action"

// ExportQueryKey is for requesting a public export of some data.
ExportQueryKey = "export"
Expand Down Expand Up @@ -63,5 +69,6 @@ func (m *Module) Route(r router.Router) error {
r.AttachHandler(http.MethodGet, DomainBlocksPath, m.DomainBlocksGETHandler)
r.AttachHandler(http.MethodGet, DomainBlocksPathWithID, m.DomainBlockGETHandler)
r.AttachHandler(http.MethodDelete, DomainBlocksPathWithID, m.DomainBlockDELETEHandler)
r.AttachHandler(http.MethodPost, AccountsActionPath, m.AccountActionPOSTHandler)
return nil
}
2 changes: 0 additions & 2 deletions internal/api/client/admin/admin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,13 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/media"
"github.com/superseriousbusiness/gotosocial/internal/oauth"
"github.com/superseriousbusiness/gotosocial/internal/processing"
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
"github.com/superseriousbusiness/gotosocial/testrig"
)

type AdminStandardTestSuite struct {
// standard suite interfaces
suite.Suite
db db.DB
tc typeutils.TypeConverter
storage *kv.KVStore
mediaManager media.Manager
federator federation.Federator
Expand Down
12 changes: 12 additions & 0 deletions internal/api/model/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,15 @@ type AdminReportInfo struct {
// Statuses attached to the report, for context.
Statuses []Status `json:"statuses"`
}

// AdminAccountActionRequest models the admin view of an account's details.
//
// swagger:ignore
type AdminAccountActionRequest struct {
// Type of the account action. One of disable, silence, suspend.
Type string `form:"type" json:"type" xml:"type"`
// Text describing why an action was taken.
Text string `form:"text" json:"text" xml:"text"`
// ID of the account to be acted on.
TargetAccountID string `form:"-" json:"-" xml:"-"`
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
GoToSocial
Copyright (C) 2021-2022 GoToSocial Authors [email protected]
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 migrations

import (
"context"

gtsmodel "github.com/superseriousbusiness/gotosocial/internal/db/bundb/migrations/20220315160814_admin_account_actions"
"github.com/uptrace/bun"
)

func init() {
up := func(ctx context.Context, db *bun.DB) error {
return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
// create table for the new admin action struct
if _, err := db.NewCreateTable().Model(&gtsmodel.AdminAccountAction{}).IfNotExists().Exec(ctx); err != nil {
return err
}

// create indexes for the new admin action struct for things we will select on
if _, err := tx.
NewCreateIndex().
Model(&gtsmodel.AdminAccountAction{}).
Index("admin_account_actions_account_id_idx").
Column("account_id").
Exec(ctx); err != nil {
return err
}

if _, err := tx.
NewCreateIndex().
Model(&gtsmodel.AdminAccountAction{}).
Index("admin_account_actions_target_account_id_idx").
Column("target_account_id").
Exec(ctx); err != nil {
return err
}

if _, err := tx.
NewCreateIndex().
Model(&gtsmodel.AdminAccountAction{}).
Index("admin_account_actions_type_idx").
Column("type").
Exec(ctx); err != nil {
return err
}

return nil
})
}

down := func(ctx context.Context, db *bun.DB) error {
return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
return nil
})
}

if err := Migrations.Register(up, down); err != nil {
panic(err)
}
}
Loading

0 comments on commit 55ad6de

Please sign in to comment.