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 }