Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add command to bulk set must-change-password #22823

Merged
406 changes: 0 additions & 406 deletions cmd/admin.go

Large diffs are not rendered by default.

21 changes: 21 additions & 0 deletions cmd/admin_user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package cmd

import (
"github.com/urfave/cli"
)

var subcmdUser = cli.Command{
Name: "user",
Usage: "Modify users",
Subcommands: []cli.Command{
microcmdUserCreate,
microcmdUserList,
microcmdUserChangePassword,
microcmdUserDelete,
microcmdUserGenerateAccessToken,
microcmdUserMustChangePassword,
},
}
76 changes: 76 additions & 0 deletions cmd/admin_user_change_password.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package cmd

import (
"context"
"errors"
"fmt"

user_model "code.gitea.io/gitea/models/user"
pwd "code.gitea.io/gitea/modules/password"
"code.gitea.io/gitea/modules/setting"

"github.com/urfave/cli"
)

var microcmdUserChangePassword = cli.Command{
Name: "change-password",
Usage: "Change a user's password",
Action: runChangePassword,
Flags: []cli.Flag{
cli.StringFlag{
Name: "username,u",
Value: "",
Usage: "The user to change password for",
},
cli.StringFlag{
Name: "password,p",
Value: "",
Usage: "New password to set for user",
},
},
}

func runChangePassword(c *cli.Context) error {
if err := argsSet(c, "username", "password"); err != nil {
return err
}

ctx, cancel := installSignals()
defer cancel()

if err := initDB(ctx); err != nil {
return err
}
if len(c.String("password")) < setting.MinPasswordLength {
return fmt.Errorf("Password is not long enough. Needs to be at least %d", setting.MinPasswordLength)
}

if !pwd.IsComplexEnough(c.String("password")) {
return errors.New("Password does not meet complexity requirements")
}
pwned, err := pwd.IsPwned(context.Background(), c.String("password"))
if err != nil {
return err
}
if pwned {
return errors.New("The password you chose is on a list of stolen passwords previously exposed in public data breaches. Please try again with a different password.\nFor more details, see https://haveibeenpwned.com/Passwords")
}
uname := c.String("username")
user, err := user_model.GetUserByName(ctx, uname)
if err != nil {
return err
}
if err = user.SetPassword(c.String("password")); err != nil {
return err
}

if err = user_model.UpdateUserCols(ctx, user, "passwd", "passwd_hash_algo", "salt"); err != nil {
return err
}

fmt.Printf("%s's password has been successfully updated!\n", user.Name)
return nil
}
169 changes: 169 additions & 0 deletions cmd/admin_user_create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package cmd

import (
"errors"
"fmt"
"os"

auth_model "code.gitea.io/gitea/models/auth"
user_model "code.gitea.io/gitea/models/user"
pwd "code.gitea.io/gitea/modules/password"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"

"github.com/urfave/cli"
)

var microcmdUserCreate = cli.Command{
Name: "create",
Usage: "Create a new user in database",
Action: runCreateUser,
Flags: []cli.Flag{
cli.StringFlag{
Name: "name",
Usage: "Username. DEPRECATED: use username instead",
},
cli.StringFlag{
Name: "username",
Usage: "Username",
},
cli.StringFlag{
Name: "password",
Usage: "User password",
},
cli.StringFlag{
Name: "email",
Usage: "User email address",
},
cli.BoolFlag{
Name: "admin",
Usage: "User is an admin",
},
cli.BoolFlag{
Name: "random-password",
Usage: "Generate a random password for the user",
},
cli.BoolFlag{
Name: "must-change-password",
Usage: "Set this option to false to prevent forcing the user to change their password after initial login, (Default: true)",
},
cli.IntFlag{
Name: "random-password-length",
Usage: "Length of the random password to be generated",
Value: 12,
},
cli.BoolFlag{
Name: "access-token",
Usage: "Generate access token for the user",
},
cli.BoolFlag{
Name: "restricted",
Usage: "Make a restricted user account",
},
},
}

func runCreateUser(c *cli.Context) error {
if err := argsSet(c, "email"); err != nil {
return err
}

if c.IsSet("name") && c.IsSet("username") {
return errors.New("Cannot set both --name and --username flags")
}
if !c.IsSet("name") && !c.IsSet("username") {
return errors.New("One of --name or --username flags must be set")
}

if c.IsSet("password") && c.IsSet("random-password") {
return errors.New("cannot set both -random-password and -password flags")
}

var username string
if c.IsSet("username") {
username = c.String("username")
} else {
username = c.String("name")
fmt.Fprintf(os.Stderr, "--name flag is deprecated. Use --username instead.\n")
}

ctx, cancel := installSignals()
defer cancel()

if err := initDB(ctx); err != nil {
return err
}

var password string
if c.IsSet("password") {
password = c.String("password")
} else if c.IsSet("random-password") {
var err error
password, err = pwd.Generate(c.Int("random-password-length"))
if err != nil {
return err
}
fmt.Printf("generated random password is '%s'\n", password)
} else {
return errors.New("must set either password or random-password flag")
}

// always default to true
changePassword := true

// If this is the first user being created.
// Take it as the admin and don't force a password update.
if n := user_model.CountUsers(nil); n == 0 {
changePassword = false
}

if c.IsSet("must-change-password") {
changePassword = c.Bool("must-change-password")
}

restricted := util.OptionalBoolNone

if c.IsSet("restricted") {
restricted = util.OptionalBoolOf(c.Bool("restricted"))
}

// default user visibility in app.ini
visibility := setting.Service.DefaultUserVisibilityMode

u := &user_model.User{
Name: username,
Email: c.String("email"),
Passwd: password,
IsAdmin: c.Bool("admin"),
MustChangePassword: changePassword,
Visibility: visibility,
}

overwriteDefault := &user_model.CreateUserOverwriteOptions{
IsActive: util.OptionalBoolTrue,
IsRestricted: restricted,
}

if err := user_model.CreateUser(u, overwriteDefault); err != nil {
return fmt.Errorf("CreateUser: %w", err)
}

if c.Bool("access-token") {
t := &auth_model.AccessToken{
Name: "gitea-admin",
UID: u.ID,
}

if err := auth_model.NewAccessToken(t); err != nil {
return err
}

fmt.Printf("Access token was successfully created... %s\n", t.Token)
}

fmt.Printf("New user '%s' has been successfully created!\n", username)
return nil
}
78 changes: 78 additions & 0 deletions cmd/admin_user_delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package cmd

import (
"fmt"
"strings"

user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/storage"
user_service "code.gitea.io/gitea/services/user"

"github.com/urfave/cli"
)

var microcmdUserDelete = cli.Command{
Name: "delete",
Usage: "Delete specific user by id, name or email",
Flags: []cli.Flag{
cli.Int64Flag{
Name: "id",
Usage: "ID of user of the user to delete",
},
cli.StringFlag{
Name: "username,u",
Usage: "Username of the user to delete",
},
cli.StringFlag{
Name: "email,e",
Usage: "Email of the user to delete",
},
cli.BoolFlag{
Name: "purge",
Usage: "Purge user, all their repositories, organizations and comments",
},
},
Action: runDeleteUser,
}

func runDeleteUser(c *cli.Context) error {
if !c.IsSet("id") && !c.IsSet("username") && !c.IsSet("email") {
return fmt.Errorf("You must provide the id, username or email of a user to delete")
}

ctx, cancel := installSignals()
defer cancel()

if err := initDB(ctx); err != nil {
return err
}

if err := storage.Init(); err != nil {
return err
}

var err error
var user *user_model.User
if c.IsSet("email") {
user, err = user_model.GetUserByEmail(c.String("email"))
} else if c.IsSet("username") {
user, err = user_model.GetUserByName(ctx, c.String("username"))
} else {
user, err = user_model.GetUserByID(ctx, c.Int64("id"))
}
if err != nil {
return err
}
if c.IsSet("username") && user.LowerName != strings.ToLower(strings.TrimSpace(c.String("username"))) {
return fmt.Errorf("The user %s who has email %s does not match the provided username %s", user.Name, c.String("email"), c.String("username"))
}

if c.IsSet("id") && user.ID != c.Int64("id") {
return fmt.Errorf("The user %s does not match the provided id %d", user.Name, c.Int64("id"))
}

return user_service.DeleteUser(ctx, user, c.Bool("purge"))
}
Loading