github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/cmd/juju/user/login.go (about) 1 // Copyright 2016 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/names" 12 "gopkg.in/macaroon.v1" 13 14 "github.com/juju/juju/api/usermanager" 15 "github.com/juju/juju/cmd/modelcmd" 16 "github.com/juju/juju/juju" 17 "github.com/juju/juju/jujuclient" 18 ) 19 20 const loginDoc = ` 21 Log in to the controller. 22 23 After logging in successfully, the client system will 24 be updated such that any previously recorded password 25 will be removed from disk, and a temporary, time-limited 26 credential written in its place. Once the credential 27 expires, you will be prompted to run "juju login" again. 28 29 Examples: 30 # Log in as the current user for the controller. 31 juju login 32 33 # Log in as the user "bob". 34 juju login bob 35 36 ` 37 38 // NewLoginCommand returns a new cmd.Command to handle "juju login". 39 func NewLoginCommand() cmd.Command { 40 return modelcmd.WrapController(&loginCommand{ 41 newLoginAPI: func(args juju.NewAPIConnectionParams) (LoginAPI, error) { 42 api, err := juju.NewAPIConnection(args) 43 if err != nil { 44 return nil, errors.Trace(err) 45 } 46 return usermanager.NewClient(api), nil 47 }, 48 }) 49 } 50 51 // loginCommand changes the password for a user. 52 type loginCommand struct { 53 modelcmd.ControllerCommandBase 54 newLoginAPI func(juju.NewAPIConnectionParams) (LoginAPI, error) 55 User string 56 } 57 58 // Info implements Command.Info. 59 func (c *loginCommand) Info() *cmd.Info { 60 return &cmd.Info{ 61 Name: "login", 62 Args: "[username]", 63 Purpose: "logs in to the controller", 64 Doc: loginDoc, 65 } 66 } 67 68 // Init implements Command.Init. 69 func (c *loginCommand) Init(args []string) error { 70 var err error 71 c.User, err = cmd.ZeroOrOneArgs(args) 72 if err != nil { 73 return errors.Trace(err) 74 } 75 return nil 76 } 77 78 // LoginAPI provides the API methods that the login command uses. 79 type LoginAPI interface { 80 CreateLocalLoginMacaroon(names.UserTag) (*macaroon.Macaroon, error) 81 Close() error 82 } 83 84 // Run implements Command.Run. 85 func (c *loginCommand) Run(ctx *cmd.Context) error { 86 controllerName := c.ControllerName() 87 store := c.ClientStore() 88 89 user := c.User 90 if user == "" { 91 // The username has not been specified, so prompt for it. 92 fmt.Fprint(ctx.Stderr, "username: ") 93 var err error 94 user, err = readLine(ctx.Stdin) 95 if err != nil { 96 return errors.Trace(err) 97 } 98 if user == "" { 99 return errors.Errorf("you must specify a username") 100 } 101 } 102 if !names.IsValidUserName(user) { 103 return errors.NotValidf("user name %q", user) 104 } 105 userTag := names.NewUserTag(user) 106 accountName := userTag.Canonical() 107 108 // Make sure that the client is not already logged in, 109 // or if it is, that it is logged in as the specified 110 // user. 111 currentAccountName, err := store.CurrentAccount(controllerName) 112 if err == nil { 113 if currentAccountName != accountName { 114 return errors.New(`already logged in 115 116 Run "juju logout" first before attempting to log in as a different user. 117 `) 118 } 119 } else if !errors.IsNotFound(err) { 120 return errors.Trace(err) 121 } 122 accountDetails, err := store.AccountByName(controllerName, accountName) 123 if err != nil && !errors.IsNotFound(err) { 124 return errors.Trace(err) 125 } 126 127 // Read password from the terminal, and attempt to log in using that. 128 fmt.Fprint(ctx.Stderr, "password: ") 129 password, err := readPassword(ctx.Stdin) 130 fmt.Fprintln(ctx.Stderr) 131 if err != nil { 132 return errors.Trace(err) 133 } 134 params, err := c.NewAPIConnectionParams(store, controllerName, "", "") 135 if err != nil { 136 return errors.Trace(err) 137 } 138 if accountDetails != nil { 139 accountDetails.Password = password 140 } else { 141 accountDetails = &jujuclient.AccountDetails{ 142 User: accountName, 143 Password: password, 144 } 145 } 146 params.AccountDetails = accountDetails 147 api, err := c.newLoginAPI(params) 148 if err != nil { 149 return errors.Annotate(err, "creating API connection") 150 } 151 defer api.Close() 152 153 // Create a new local login macaroon, and update the account details 154 // in the client store, removing the recorded password (if any) and 155 // storing the macaroon. 156 macaroon, err := api.CreateLocalLoginMacaroon(userTag) 157 if err != nil { 158 return errors.Annotate(err, "failed to create a temporary credential") 159 } 160 macaroonJSON, err := macaroon.MarshalJSON() 161 if err != nil { 162 return errors.Annotate(err, "marshalling temporary credential to JSON") 163 } 164 accountDetails.Password = "" 165 accountDetails.Macaroon = string(macaroonJSON) 166 if err := store.UpdateAccount(controllerName, accountName, *accountDetails); err != nil { 167 return errors.Annotate(err, "failed to record temporary credential") 168 } 169 if err := store.SetCurrentAccount(controllerName, accountName); err != nil { 170 return errors.Annotate(err, "failed to set current account") 171 } 172 ctx.Infof("You are now logged in to %q as %q.", controllerName, accountName) 173 return nil 174 }