}, | }, | ||||
&cli.BoolFlag{ | &cli.BoolFlag{ | ||||
Name: "must-change-password", | Name: "must-change-password", | ||||
Usage: "User must change password", | |||||
Usage: "User must change password (can be disabled by --must-change-password=false)", | |||||
Value: true, | Value: true, | ||||
}, | }, | ||||
}, | }, |
package cmd | package cmd | ||||
import ( | import ( | ||||
"context" | |||||
"errors" | "errors" | ||||
"fmt" | "fmt" | ||||
}, | }, | ||||
&cli.BoolFlag{ | &cli.BoolFlag{ | ||||
Name: "must-change-password", | Name: "must-change-password", | ||||
Usage: "Set to false to prevent forcing the user to change their password after initial login", | |||||
Usage: "User must change password after initial login, defaults to true for all users except the first admin user (can be disabled by --must-change-password=false)", | |||||
DisableDefaultText: true, | DisableDefaultText: true, | ||||
}, | }, | ||||
&cli.IntFlag{ | &cli.IntFlag{ | ||||
_, _ = fmt.Fprintf(c.App.ErrWriter, "--name flag is deprecated. Use --username instead.\n") | _, _ = fmt.Fprintf(c.App.ErrWriter, "--name flag is deprecated. Use --username instead.\n") | ||||
} | } | ||||
ctx, cancel := installSignals() | |||||
defer cancel() | |||||
if err := initDB(ctx); err != nil { | |||||
return err | |||||
ctx := c.Context | |||||
if !setting.IsInTesting { | |||||
// FIXME: need to refactor the "installSignals/initDB" related code later | |||||
// it doesn't make sense to call it in (almost) every command action function | |||||
var cancel context.CancelFunc | |||||
ctx, cancel = installSignals() | |||||
defer cancel() | |||||
if err := initDB(ctx); err != nil { | |||||
return err | |||||
} | |||||
} | } | ||||
var password string | var password string |
// Copyright 2023 The Gitea Authors. All rights reserved. | |||||
// SPDX-License-Identifier: MIT | |||||
package cmd | |||||
import ( | |||||
"fmt" | |||||
"strings" | |||||
"testing" | |||||
"code.gitea.io/gitea/models/db" | |||||
"code.gitea.io/gitea/models/unittest" | |||||
user_model "code.gitea.io/gitea/models/user" | |||||
"github.com/stretchr/testify/assert" | |||||
) | |||||
func TestAdminUserCreate(t *testing.T) { | |||||
app := NewMainApp(AppVersion{}) | |||||
reset := func() { | |||||
assert.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.User{})) | |||||
assert.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.EmailAddress{})) | |||||
} | |||||
type createCheck struct{ IsAdmin, MustChangePassword bool } | |||||
createUser := func(name, args string) createCheck { | |||||
assert.NoError(t, app.Run(strings.Fields(fmt.Sprintf("./gitea admin user create --username %s --email %s@gitea.local %s --password foobar", name, name, args)))) | |||||
u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: name}) | |||||
return createCheck{u.IsAdmin, u.MustChangePassword} | |||||
} | |||||
reset() | |||||
assert.Equal(t, createCheck{IsAdmin: false, MustChangePassword: true}, createUser("u", ""), "first non-admin user must change password") | |||||
reset() | |||||
assert.Equal(t, createCheck{IsAdmin: true, MustChangePassword: false}, createUser("u", "--admin"), "first admin user doesn't need to change password") | |||||
reset() | |||||
assert.Equal(t, createCheck{IsAdmin: true, MustChangePassword: true}, createUser("u", "--admin --must-change-password")) | |||||
assert.Equal(t, createCheck{IsAdmin: true, MustChangePassword: true}, createUser("u2", "--admin")) | |||||
assert.Equal(t, createCheck{IsAdmin: true, MustChangePassword: false}, createUser("u3", "--admin --must-change-password=false")) | |||||
assert.Equal(t, createCheck{IsAdmin: false, MustChangePassword: true}, createUser("u4", "")) | |||||
assert.Equal(t, createCheck{IsAdmin: false, MustChangePassword: false}, createUser("u5", "--must-change-password=false")) | |||||
} |
} | } | ||||
} | } | ||||
func NewMainApp(version, versionExtra string) *cli.App { | |||||
type AppVersion struct { | |||||
Version string | |||||
Extra string | |||||
} | |||||
func NewMainApp(appVer AppVersion) *cli.App { | |||||
app := cli.NewApp() | app := cli.NewApp() | ||||
app.Name = "Gitea" | app.Name = "Gitea" | ||||
app.HelpName = "gitea" | app.HelpName = "gitea" | ||||
app.Usage = "A painless self-hosted Git service" | app.Usage = "A painless self-hosted Git service" | ||||
app.Description = `Gitea program contains "web" and other subcommands. If no subcommand is given, it starts the web server by default. Use "web" subcommand for more web server arguments, use other subcommands for other purposes.` | app.Description = `Gitea program contains "web" and other subcommands. If no subcommand is given, it starts the web server by default. Use "web" subcommand for more web server arguments, use other subcommands for other purposes.` | ||||
app.Version = version + versionExtra | |||||
app.Version = appVer.Version + appVer.Extra | |||||
app.EnableBashCompletion = true | app.EnableBashCompletion = true | ||||
// these sub-commands need to use config file | // these sub-commands need to use config file |
} | } | ||||
func newTestApp(testCmdAction func(ctx *cli.Context) error) *cli.App { | func newTestApp(testCmdAction func(ctx *cli.Context) error) *cli.App { | ||||
app := NewMainApp("version", "version-extra") | |||||
app := NewMainApp(AppVersion{}) | |||||
testCmd := &cli.Command{Name: "test-cmd", Action: testCmdAction} | testCmd := &cli.Command{Name: "test-cmd", Action: testCmdAction} | ||||
prepareSubcommandWithConfig(testCmd, appGlobalFlags()) | prepareSubcommandWithConfig(testCmd, appGlobalFlags()) | ||||
app.Commands = append(app.Commands, testCmd) | app.Commands = append(app.Commands, testCmd) |
log.GetManager().Close() | log.GetManager().Close() | ||||
os.Exit(code) | os.Exit(code) | ||||
} | } | ||||
app := cmd.NewMainApp(Version, formatBuiltWith()) | |||||
app := cmd.NewMainApp(cmd.AppVersion{Version: Version, Extra: formatBuiltWith()}) | |||||
_ = cmd.RunMainApp(app, os.Args...) // all errors should have been handled by the RunMainApp | _ = cmd.RunMainApp(app, os.Args...) // all errors should have been handled by the RunMainApp | ||||
log.GetManager().Close() | log.GetManager().Close() | ||||
} | } |
import ( | import ( | ||||
"context" | "context" | ||||
"fmt" | "fmt" | ||||
"log" | |||||
"os" | "os" | ||||
"path/filepath" | "path/filepath" | ||||
"strings" | "strings" | ||||
"code.gitea.io/gitea/modules/auth/password/hash" | "code.gitea.io/gitea/modules/auth/password/hash" | ||||
"code.gitea.io/gitea/modules/base" | "code.gitea.io/gitea/modules/base" | ||||
"code.gitea.io/gitea/modules/git" | "code.gitea.io/gitea/modules/git" | ||||
"code.gitea.io/gitea/modules/log" | |||||
"code.gitea.io/gitea/modules/setting" | "code.gitea.io/gitea/modules/setting" | ||||
"code.gitea.io/gitea/modules/setting/config" | "code.gitea.io/gitea/modules/setting/config" | ||||
"code.gitea.io/gitea/modules/storage" | "code.gitea.io/gitea/modules/storage" | ||||
// InitSettings initializes config provider and load common settings for tests | // InitSettings initializes config provider and load common settings for tests | ||||
func InitSettings() { | func InitSettings() { | ||||
setting.IsInTesting = true | |||||
log.OsExiter = func(code int) { | |||||
if code != 0 { | |||||
// non-zero exit code (log.Fatal) shouldn't occur during testing, if it happens, show a full stacktrace for more details | |||||
panic(fmt.Errorf("non-zero exit code during testing: %d", code)) | |||||
} | |||||
os.Exit(0) | |||||
} | |||||
if setting.CustomConf == "" { | if setting.CustomConf == "" { | ||||
setting.CustomConf = filepath.Join(setting.CustomPath, "conf/app-unittest-tmp.ini") | setting.CustomConf = filepath.Join(setting.CustomPath, "conf/app-unittest-tmp.ini") | ||||
_ = os.Remove(setting.CustomConf) | _ = os.Remove(setting.CustomConf) | ||||
setting.LoadCommonSettings() | setting.LoadCommonSettings() | ||||
if err := setting.PrepareAppDataPath(); err != nil { | if err := setting.PrepareAppDataPath(); err != nil { | ||||
log.Fatalf("Can not prepare APP_DATA_PATH: %v", err) | |||||
log.Fatal("Can not prepare APP_DATA_PATH: %v", err) | |||||
} | } | ||||
// register the dummy hash algorithm function used in the test fixtures | // register the dummy hash algorithm function used in the test fixtures | ||||
_ = hash.Register("dummy", hash.NewDummyHasher) | _ = hash.Register("dummy", hash.NewDummyHasher) |
Log(1, ERROR, format, v...) | Log(1, ERROR, format, v...) | ||||
} | } | ||||
var OsExiter = os.Exit | |||||
// Fatal records fatal log and exit process | // Fatal records fatal log and exit process | ||||
func Fatal(format string, v ...any) { | func Fatal(format string, v ...any) { | ||||
Log(1, FATAL, format, v...) | Log(1, FATAL, format, v...) | ||||
GetManager().Close() | GetManager().Close() | ||||
os.Exit(1) | |||||
OsExiter(1) | |||||
} | } | ||||
func GetLogger(name string) Logger { | func GetLogger(name string) Logger { |
// TODO: Speedup tests that rely on the event source ticker, confirm whether there is any bug or failure. | // TODO: Speedup tests that rely on the event source ticker, confirm whether there is any bug or failure. | ||||
// setting.UI.Notification.EventSourceUpdateTime = time.Second | // setting.UI.Notification.EventSourceUpdateTime = time.Second | ||||
setting.IsInTesting = true | |||||
setting.AppWorkPath = giteaRoot | setting.AppWorkPath = giteaRoot | ||||
setting.CustomPath = filepath.Join(setting.AppWorkPath, "custom") | setting.CustomPath = filepath.Join(setting.AppWorkPath, "custom") | ||||
if requireGitea { | if requireGitea { |