diff options
| -rw-r--r-- | cmd/gotosocial/main.go | 103 | ||||
| -rw-r--r-- | internal/clitools/admin/account/account.go | 209 | ||||
| -rw-r--r-- | internal/config/config.go | 41 | ||||
| -rw-r--r-- | internal/message/frprocess.go | 13 | 
4 files changed, 364 insertions, 2 deletions
diff --git a/cmd/gotosocial/main.go b/cmd/gotosocial/main.go index 948215276..a113ef2dc 100644 --- a/cmd/gotosocial/main.go +++ b/cmd/gotosocial/main.go @@ -24,6 +24,7 @@ import (  	"github.com/sirupsen/logrus"  	"github.com/superseriousbusiness/gotosocial/internal/action" +	"github.com/superseriousbusiness/gotosocial/internal/clitools/admin/account"  	"github.com/superseriousbusiness/gotosocial/internal/config"  	"github.com/superseriousbusiness/gotosocial/internal/db"  	"github.com/superseriousbusiness/gotosocial/internal/gotosocial" @@ -264,6 +265,104 @@ func main() {  				},  			},  			{ +				Name:  "admin", +				Usage: "gotosocial admin-related tasks", +				Subcommands: []*cli.Command{ +					{ +						Name:  "account", +						Usage: "admin commands related to accounts", +						Subcommands: []*cli.Command{ +							{ +								Name:  "create", +								Usage: "create a new account", +								Flags: []cli.Flag{ +									&cli.StringFlag{ +										Name:    config.UsernameFlag, +										Usage:   config.UsernameUsage, +									}, +									&cli.StringFlag{ +										Name:    config.EmailFlag, +										Usage:   config.EmailUsage, +									}, +									&cli.StringFlag{ +										Name:    config.PasswordFlag, +										Usage:   config.PasswordUsage, +									}, +								}, +								Action: func(c *cli.Context) error { +									return runAction(c, account.Create) +								}, +							}, +							{ +								Name:  "confirm", +								Usage: "confirm an existing account manually, thereby skipping email confirmation", +								Flags: []cli.Flag{ +									&cli.StringFlag{ +										Name:    config.UsernameFlag, +										Usage:   config.UsernameUsage, +									}, +								}, +								Action: func(c *cli.Context) error { +									return runAction(c, account.Confirm) +								}, +							}, +							{ +								Name:  "promote", +								Usage: "promote an account to admin", +								Flags: []cli.Flag{ +									&cli.StringFlag{ +										Name:    config.UsernameFlag, +										Usage:   config.UsernameUsage, +									}, +								}, +								Action: func(c *cli.Context) error { +									return runAction(c, account.Promote) +								}, +							}, +							{ +								Name:  "demote", +								Usage: "demote an account from admin to normal user", +								Flags: []cli.Flag{ +									&cli.StringFlag{ +										Name:    config.UsernameFlag, +										Usage:   config.UsernameUsage, +									}, +								}, +								Action: func(c *cli.Context) error { +									return runAction(c, account.Demote) +								}, +							}, +							{ +								Name:  "disable", +								Usage: "prevent an account from signing in or posting etc, but don't delete anything", +								Flags: []cli.Flag{ +									&cli.StringFlag{ +										Name:    config.UsernameFlag, +										Usage:   config.UsernameUsage, +									}, +								}, +								Action: func(c *cli.Context) error { +									return runAction(c, account.Disable) +								}, +							}, +							{ +								Name:  "suspend", +								Usage: "completely remove an account and all of its posts, media, etc", +								Flags: []cli.Flag{ +									&cli.StringFlag{ +										Name:    config.UsernameFlag, +										Usage:   config.UsernameUsage, +									}, +								}, +								Action: func(c *cli.Context) error { +									return runAction(c, account.Suspend) +								}, +							}, +						}, +					}, +				}, +			}, +			{  				Name:  "db",  				Usage: "database-related tasks and utils",  				Subcommands: []*cli.Command{ @@ -308,7 +407,9 @@ func runAction(c *cli.Context, a action.GTSAction) error {  		return fmt.Errorf("error creating config: %s", err)  	}  	// ... and the flags set on the *cli.Context by urfave -	conf.ParseCLIFlags(c) +	if err := conf.ParseCLIFlags(c); err != nil { +		return fmt.Errorf("error parsing config: %s", err) +	}  	// create a logger with the log level, formatting, and output splitter already set  	log, err := log.New(conf.LogLevel) diff --git a/internal/clitools/admin/account/account.go b/internal/clitools/admin/account/account.go new file mode 100644 index 000000000..da4de4711 --- /dev/null +++ b/internal/clitools/admin/account/account.go @@ -0,0 +1,209 @@ +/* +   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 <http://www.gnu.org/licenses/>. +*/ + +package account + +import ( +	"context" +	"errors" +	"fmt" +	"time" + +	"github.com/sirupsen/logrus" +	"github.com/superseriousbusiness/gotosocial/internal/action" +	"github.com/superseriousbusiness/gotosocial/internal/config" +	"github.com/superseriousbusiness/gotosocial/internal/db" +	"github.com/superseriousbusiness/gotosocial/internal/db/pg" +	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" +	"github.com/superseriousbusiness/gotosocial/internal/util" +) + +// Create creates a new account in the database using the provided flags. +var Create action.GTSAction = func(ctx context.Context, c *config.Config, log *logrus.Logger) error { +	dbConn, err := pg.NewPostgresService(ctx, c, log) +	if err != nil { +		return fmt.Errorf("error creating dbservice: %s", err) +	} + +	username, ok := c.AccountCLIFlags[config.UsernameFlag] +	if !ok { +		return errors.New("no username set") +	} +	if err := util.ValidateUsername(username); err != nil { +		return err +	} + +	email, ok := c.AccountCLIFlags[config.EmailFlag] +	if !ok { +		return errors.New("no email set") +	} +	if err := util.ValidateEmail(email); err != nil { +		return err +	} + +	password, ok := c.AccountCLIFlags[config.PasswordFlag] +	if !ok { +		return errors.New("no password set") +	} +	if err := util.ValidateNewPassword(password); err != nil { +		return err +	} + +	_, err = dbConn.NewSignup(username, "", false, email, password, nil, "", "") +	if err != nil { +		return err +	} + +	return dbConn.Stop(ctx) +} + +// Confirm sets a user to Approved, sets Email to the current UnconfirmedEmail value, and sets ConfirmedAt to now. +var Confirm action.GTSAction = func(ctx context.Context, c *config.Config, log *logrus.Logger) error { +	dbConn, err := pg.NewPostgresService(ctx, c, log) +	if err != nil { +		return fmt.Errorf("error creating dbservice: %s", err) +	} + +	username, ok := c.AccountCLIFlags[config.UsernameFlag] +	if !ok { +		return errors.New("no username set") +	} +	if err := util.ValidateUsername(username); err != nil { +		return err +	} + +	a := >smodel.Account{} +	if err := dbConn.GetLocalAccountByUsername(username, a); err != nil { +		return err +	} + +	u := >smodel.User{} +	if err := dbConn.GetWhere([]db.Where{{Key: "account_id", Value: a.ID}}, u); err != nil { +		return err +	} + +	u.Approved = true +	u.Email = u.UnconfirmedEmail +	u.ConfirmedAt = time.Now() +	if err := dbConn.UpdateByID(u.ID, u); err != nil { +		return err +	} + +	return dbConn.Stop(ctx) +} + +// Promote sets a user to admin. +var Promote action.GTSAction = func(ctx context.Context, c *config.Config, log *logrus.Logger) error { +	dbConn, err := pg.NewPostgresService(ctx, c, log) +	if err != nil { +		return fmt.Errorf("error creating dbservice: %s", err) +	} + +	username, ok := c.AccountCLIFlags[config.UsernameFlag] +	if !ok { +		return errors.New("no username set") +	} +	if err := util.ValidateUsername(username); err != nil { +		return err +	} + +	a := >smodel.Account{} +	if err := dbConn.GetLocalAccountByUsername(username, a); err != nil { +		return err +	} + +	u := >smodel.User{} +	if err := dbConn.GetWhere([]db.Where{{Key: "account_id", Value: a.ID}}, u); err != nil { +		return err +	} +	u.Admin = true +	if err := dbConn.UpdateByID(u.ID, u); err != nil { +		return err +	} + +	return dbConn.Stop(ctx) +} + +// Demote sets admin on a user to false. +var Demote action.GTSAction = func(ctx context.Context, c *config.Config, log *logrus.Logger) error { +	dbConn, err := pg.NewPostgresService(ctx, c, log) +	if err != nil { +		return fmt.Errorf("error creating dbservice: %s", err) +	} + +	username, ok := c.AccountCLIFlags[config.UsernameFlag] +	if !ok { +		return errors.New("no username set") +	} +	if err := util.ValidateUsername(username); err != nil { +		return err +	} + +	a := >smodel.Account{} +	if err := dbConn.GetLocalAccountByUsername(username, a); err != nil { +		return err +	} + +	u := >smodel.User{} +	if err := dbConn.GetWhere([]db.Where{{Key: "account_id", Value: a.ID}}, u); err != nil { +		return err +	} +	u.Admin = false +	if err := dbConn.UpdateByID(u.ID, u); err != nil { +		return err +	} + +	return dbConn.Stop(ctx) +} + +// Disable sets Disabled to true on a user. +var Disable action.GTSAction = func(ctx context.Context, c *config.Config, log *logrus.Logger) error { +	dbConn, err := pg.NewPostgresService(ctx, c, log) +	if err != nil { +		return fmt.Errorf("error creating dbservice: %s", err) +	} + +	username, ok := c.AccountCLIFlags[config.UsernameFlag] +	if !ok { +		return errors.New("no username set") +	} +	if err := util.ValidateUsername(username); err != nil { +		return err +	} + +	a := >smodel.Account{} +	if err := dbConn.GetLocalAccountByUsername(username, a); err != nil { +		return err +	} + +	u := >smodel.User{} +	if err := dbConn.GetWhere([]db.Where{{Key: "account_id", Value: a.ID}}, u); err != nil { +		return err +	} +	u.Disabled = true +	if err := dbConn.UpdateByID(u.ID, u); err != nil { +		return err +	} + +	return dbConn.Stop(ctx) +} + +var Suspend action.GTSAction = func(ctx context.Context, c *config.Config, log *logrus.Logger) error { +	// TODO +	return nil +} diff --git a/internal/config/config.go b/internal/config/config.go index 23f7d0d1c..c06f45384 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -19,14 +19,31 @@  package config  import ( +	"errors"  	"fmt"  	"os"  	"gopkg.in/yaml.v2"  ) +const ( +	UsernameFlag  = "username" +	UsernameUsage = "the username to create/delete/etc" + +	EmailFlag  = "email" +	EmailUsage = "the email address of this account" + +	PasswordFlag  = "password" +	PasswordUsage = "the password to set for this account" +) +  // Config pulls together all the configuration needed to run gotosocial  type Config struct { +	/* +		Parseable from .yaml configuration file. +		For long-running commands (server start etc). +	*/ +  	LogLevel          string             `yaml:"logLevel"`  	ApplicationName   string             `yaml:"applicationName"`  	Host              string             `yaml:"host"` @@ -38,6 +55,12 @@ type Config struct {  	StorageConfig     *StorageConfig     `yaml:"storage"`  	StatusesConfig    *StatusesConfig    `yaml:"statuses"`  	LetsEncryptConfig *LetsEncryptConfig `yaml:"letsEncrypt"` + +	/* +		Not parsed from .yaml configuration file. +		For short running commands (admin CLI tools etc). +	*/ +	AccountCLIFlags map[string]string  }  // FromFile returns a new config from a file, or an error if something goes amiss. @@ -62,6 +85,7 @@ func Empty() *Config {  		StorageConfig:     &StorageConfig{},  		StatusesConfig:    &StatusesConfig{},  		LetsEncryptConfig: &LetsEncryptConfig{}, +		AccountCLIFlags:   make(map[string]string),  	}  } @@ -81,7 +105,7 @@ func loadFromFile(path string) (*Config, error) {  }  // ParseCLIFlags sets flags on the config using the provided Flags object -func (c *Config) ParseCLIFlags(f KeyedFlags) { +func (c *Config) ParseCLIFlags(f KeyedFlags) error {  	fn := GetFlagNames()  	// For all of these flags, we only want to set them on the config if: @@ -104,10 +128,16 @@ func (c *Config) ParseCLIFlags(f KeyedFlags) {  	if c.Host == "" || f.IsSet(fn.Host) {  		c.Host = f.String(fn.Host)  	} +	if c.Host == "" { +		return errors.New("host was not set") +	}  	if c.Protocol == "" || f.IsSet(fn.Protocol) {  		c.Protocol = f.String(fn.Protocol)  	} +	if c.Protocol == "" { +		return errors.New("protocol was not set") +	}  	// db flags  	if c.DBConfig.Type == "" || f.IsSet(fn.DbType) { @@ -215,6 +245,15 @@ func (c *Config) ParseCLIFlags(f KeyedFlags) {  	if c.LetsEncryptConfig.EmailAddress == "" || f.IsSet(fn.LetsEncryptEmailAddress) {  		c.LetsEncryptConfig.EmailAddress = f.String(fn.LetsEncryptEmailAddress)  	} + +	// command-specific flags + +	// admin account CLI flags +	c.AccountCLIFlags[UsernameFlag] = f.String(UsernameFlag) +	c.AccountCLIFlags[EmailFlag] = f.String(EmailFlag) +	c.AccountCLIFlags[PasswordFlag] = f.String(PasswordFlag) + +	return nil  }  // KeyedFlags is a wrapper for any type that can store keyed flags and give them back. diff --git a/internal/message/frprocess.go b/internal/message/frprocess.go index e229dcfbb..5d02836e6 100644 --- a/internal/message/frprocess.go +++ b/internal/message/frprocess.go @@ -54,9 +54,22 @@ func (p *processor) FollowRequestAccept(auth *oauth.Auth, accountID string) (*ap  		return nil, NewErrorNotFound(err)  	} +	originAccount := >smodel.Account{} +	if err := p.db.GetByID(follow.AccountID, originAccount); err != nil { +		return nil, NewErrorInternalError(err) +	} + +	targetAccount := >smodel.Account{} +	if err := p.db.GetByID(follow.TargetAccountID, targetAccount); err != nil { +		return nil, NewErrorInternalError(err) +	} +  	p.fromClientAPI <- gtsmodel.FromClientAPI{ +		APObjectType:   gtsmodel.ActivityStreamsFollow,  		APActivityType: gtsmodel.ActivityStreamsAccept,  		GTSModel:       follow, +		OriginAccount: originAccount, +		TargetAccount: targetAccount,  	}  	gtsR, err := p.db.GetRelationship(auth.Account.ID, accountID)  | 
