Files
kopia/cli/command_user_add_set.go
Jarek Kowalski 5d07237156 Added support for user authentication using user profiles stored in the repository (#809)
* user: added user profile (username&password for authentication) and CRUD methods
* manifest: helpers for disambiguating manifest entries
* authn: added repository-based user authenticator
* cli: added commands to manipulate user accounts and passwords
* cli: added --allow-repository-users option to 'server start'
* Update cli/command_user_info.go

Co-authored-by: Julio López <julio+gh@kasten.io>
* Always return false when the user is not found.
2021-02-03 22:04:05 -08:00

121 lines
3.4 KiB
Go

package cli
import (
"context"
"encoding/base64"
"github.com/alecthomas/kingpin"
"github.com/pkg/errors"
"github.com/kopia/kopia/internal/user"
"github.com/kopia/kopia/repo"
)
var (
userCreateCommand = userCommands.Command("add", "Add new repository user").Alias("create")
userUpdateCommand = userCommands.Command("set", "Set password for a repository user.").Alias("update")
userAskPassword bool
userSetName string
userSetPassword string
userSetPasswordHashVersion int = 1
userSetPasswordHash string
)
func registerAddSetUserCommandArguments(cmd *kingpin.CmdClause) {
cmd.Flag("ask-password", "Ask for user password").BoolVar(&userAskPassword)
cmd.Flag("user-password", "Password").StringVar(&userSetPassword)
cmd.Flag("user-password-hash", "Password hash").StringVar(&userSetPasswordHash)
cmd.Flag("user-password-hash-version", "Password hash version").Default("1").IntVar(&userSetPasswordHashVersion)
cmd.Arg("username", "Username").Required().StringVar(&userSetName)
}
func runUserCreate(ctx context.Context, rep repo.RepositoryWriter) error {
return runServerUserAddSet(ctx, rep, true)
}
func runUserUpdate(ctx context.Context, rep repo.RepositoryWriter) error {
return runServerUserAddSet(ctx, rep, false)
}
func getExistingOrNewUserProfile(ctx context.Context, rep repo.Repository, username string, isNew bool) (*user.Profile, error) {
up, err := user.GetUserProfile(ctx, rep, username)
if isNew {
switch {
case err == nil:
return nil, errors.Errorf("user %q already exists", username)
case errors.Is(err, user.ErrUserNotFound):
return &user.Profile{
Username: username,
}, nil
}
}
return up, errors.Wrap(err, "error getting user profile")
}
func runServerUserAddSet(ctx context.Context, rep repo.RepositoryWriter, isNew bool) error {
username := userSetName
up, err := getExistingOrNewUserProfile(ctx, rep, username, isNew)
if err != nil {
return err
}
if p := userSetPassword; p != "" {
if err := up.SetPassword(p); err != nil {
return errors.Wrap(err, "error setting password")
}
}
if p := userSetPasswordHash; p != "" {
ph, err := base64.StdEncoding.DecodeString(p)
if err != nil {
return errors.Wrap(err, "invalid password hash, must be valid base64 string")
}
up.PasswordHashVersion = userSetPasswordHashVersion
up.PasswordHash = ph
}
if up.PasswordHash == nil || userAskPassword {
pwd, err := askPass("Enter new password for user " + username + ": ")
if err != nil {
return errors.Wrap(err, "error asking for password")
}
pwd2, err := askPass("Re-enter new password for verification: ")
if err != nil {
return errors.Wrap(err, "error asking for password")
}
if pwd != pwd2 {
return errors.Wrap(err, "passwords don't match")
}
if err := up.SetPassword(pwd); err != nil {
return errors.Wrap(err, "error setting password")
}
}
if err := user.SetUserProfile(ctx, rep, up); err != nil {
return errors.Wrap(err, "error setting user profile")
}
log(ctx).Noticef(`
Updated user credentials will take effect in 5-10 minutes or when the server is restarted.
To refresh credentials in a running server use 'kopia server refresh' command.
`)
return nil
}
func init() {
registerAddSetUserCommandArguments(userCreateCommand)
registerAddSetUserCommandArguments(userUpdateCommand)
userCreateCommand.Action(repositoryWriterAction(runUserCreate))
userUpdateCommand.Action(repositoryWriterAction(runUserUpdate))
}