forked from go-gitea/gitea
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add command to bulk set must-change-password (go-gitea#22823)
Backport go-gitea#22823 As part of administration sometimes it is appropriate to forcibly tell users to update their passwords. This PR creates a new command `gitea admin user must-change-password` which will set the `MustChangePassword` flag on the provided users. Signed-off-by: Andrew Thornton <art27@cantab.net>
- Loading branch information
Showing
10 changed files
with
587 additions
and
396 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(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")) | ||
} |
Oops, something went wrong.