github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/cmd/juju/user/change_password.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package user
     5  
     6  import (
     7  	"fmt"
     8  
     9  	"github.com/juju/cmd"
    10  	"github.com/juju/errors"
    11  	"github.com/juju/utils"
    12  	"github.com/juju/utils/readpass"
    13  	"launchpad.net/gnuflag"
    14  
    15  	"github.com/juju/juju/cmd/juju/block"
    16  	"github.com/juju/juju/environs/configstore"
    17  )
    18  
    19  // randomPasswordNotify is called when a random password is generated.
    20  var randomPasswordNotify = func(string) {}
    21  
    22  const userChangePasswordDoc = `
    23  Change the password for the user you are currently logged in as,
    24  or as an admin, change the password for another user.
    25  
    26  Examples:
    27    # You will be prompted to enter a password.
    28    juju user change-password
    29  
    30    # Change the password to a random strong password.
    31    juju user change-password --generate
    32  
    33    # Change the password for bob, this always uses a random password
    34    juju user change-password bob
    35  
    36  `
    37  
    38  // ChangePasswordCommand changes the password for a user.
    39  type ChangePasswordCommand struct {
    40  	UserCommandBase
    41  	api      ChangePasswordAPI
    42  	writer   EnvironInfoCredsWriter
    43  	Generate bool
    44  	OutPath  string
    45  	User     string
    46  }
    47  
    48  // Info implements Command.Info.
    49  func (c *ChangePasswordCommand) Info() *cmd.Info {
    50  	return &cmd.Info{
    51  		Name:    "change-password",
    52  		Args:    "[username]",
    53  		Purpose: "changes the password for a user",
    54  		Doc:     userChangePasswordDoc,
    55  	}
    56  }
    57  
    58  // SetFlags implements Command.SetFlags.
    59  func (c *ChangePasswordCommand) SetFlags(f *gnuflag.FlagSet) {
    60  	f.BoolVar(&c.Generate, "generate", false, "generate a new strong password")
    61  	f.StringVar(&c.OutPath, "o", "", "specifies the path of the generated user environment file")
    62  	f.StringVar(&c.OutPath, "output", "", "")
    63  }
    64  
    65  // Init implements Command.Init.
    66  func (c *ChangePasswordCommand) Init(args []string) error {
    67  	var err error
    68  	c.User, err = cmd.ZeroOrOneArgs(args)
    69  	if c.User == "" && c.OutPath != "" {
    70  		return errors.New("output is only a valid option when changing another user's password")
    71  	}
    72  	if c.User != "" {
    73  		c.Generate = true
    74  		if c.OutPath == "" {
    75  			c.OutPath = c.User + ".server"
    76  		}
    77  	}
    78  	return err
    79  }
    80  
    81  // ChangePasswordAPI defines the usermanager API methods that the change
    82  // password command uses.
    83  type ChangePasswordAPI interface {
    84  	SetPassword(username, password string) error
    85  	Close() error
    86  }
    87  
    88  // EnvironInfoCredsWriter defines methods of the configstore API info that
    89  // are used to change the password.
    90  type EnvironInfoCredsWriter interface {
    91  	Write() error
    92  	APICredentials() configstore.APICredentials
    93  	SetAPICredentials(creds configstore.APICredentials)
    94  }
    95  
    96  // Run implements Command.Run.
    97  func (c *ChangePasswordCommand) Run(ctx *cmd.Context) error {
    98  	if c.api == nil {
    99  		api, err := c.NewUserManagerAPIClient()
   100  		if err != nil {
   101  			return errors.Trace(err)
   102  		}
   103  		c.api = api
   104  		defer c.api.Close()
   105  	}
   106  
   107  	password, err := c.generateOrReadPassword(ctx, c.Generate)
   108  	if err != nil {
   109  		return errors.Trace(err)
   110  	}
   111  
   112  	var writer EnvironInfoCredsWriter
   113  
   114  	var creds configstore.APICredentials
   115  
   116  	if c.User == "" {
   117  		// We get the creds writer before changing the password just to
   118  		// minimise the things that could go wrong after changing the password
   119  		// in the server.
   120  		if c.writer == nil {
   121  			writer, err = c.ConnectionInfo()
   122  			if err != nil {
   123  				return errors.Trace(err)
   124  			}
   125  		} else {
   126  			writer = c.writer
   127  		}
   128  
   129  		creds = writer.APICredentials()
   130  	} else {
   131  		creds.User = c.User
   132  	}
   133  
   134  	oldPassword := creds.Password
   135  	creds.Password = password
   136  	if err = c.api.SetPassword(creds.User, password); err != nil {
   137  		return block.ProcessBlockedError(err, block.BlockChange)
   138  	}
   139  
   140  	if c.User != "" {
   141  		return writeServerFile(c, ctx, c.User, password, c.OutPath)
   142  	}
   143  
   144  	writer.SetAPICredentials(creds)
   145  	if err := writer.Write(); err != nil {
   146  		logger.Errorf("updating the cached credentials failed, reverting to original password")
   147  		setErr := c.api.SetPassword(creds.User, oldPassword)
   148  		if setErr != nil {
   149  			logger.Errorf("failed to set password back, you will need to edit your environments file by hand to specify the password: %q", password)
   150  			return errors.Annotate(setErr, "failed to set password back")
   151  		}
   152  		return errors.Annotate(err, "failed to write new password to environments file")
   153  	}
   154  	ctx.Infof("Your password has been updated.")
   155  	return nil
   156  }
   157  
   158  var readPassword = readpass.ReadPassword
   159  
   160  func (*ChangePasswordCommand) generateOrReadPassword(ctx *cmd.Context, generate bool) (string, error) {
   161  	if generate {
   162  		password, err := utils.RandomPassword()
   163  		if err != nil {
   164  			return "", errors.Annotate(err, "failed to generate random password")
   165  		}
   166  		randomPasswordNotify(password)
   167  		return password, nil
   168  	}
   169  
   170  	// Don't add the carriage returns before readPassword, but add
   171  	// them directly after the readPassword so any errors are output
   172  	// on their own lines.
   173  	fmt.Fprint(ctx.Stdout, "password: ")
   174  	password, err := readPassword()
   175  	fmt.Fprint(ctx.Stdout, "\n")
   176  	if err != nil {
   177  		return "", errors.Trace(err)
   178  	}
   179  	fmt.Fprint(ctx.Stdout, "type password again: ")
   180  	verify, err := readPassword()
   181  	fmt.Fprint(ctx.Stdout, "\n")
   182  	if err != nil {
   183  		return "", errors.Trace(err)
   184  	}
   185  	if password != verify {
   186  		return "", errors.New("Passwords do not match")
   187  	}
   188  	return password, nil
   189  }