github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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 "gopkg.in/juju/names.v2" 12 13 "github.com/juju/juju/api/usermanager" 14 "github.com/juju/juju/apiserver/params" 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 After login, a token ("macaroon") will become active. It has an expiration 22 time of 24 hours. Upon expiration, no further Juju commands can be issued 23 and the user will be prompted to log in again. 24 25 Examples: 26 27 juju login bob 28 29 See also: 30 disable-user 31 enable-user 32 logout 33 34 ` 35 36 // NewLoginCommand returns a new cmd.Command to handle "juju login". 37 func NewLoginCommand() cmd.Command { 38 return modelcmd.WrapController(&loginCommand{ 39 newLoginAPI: func(args juju.NewAPIConnectionParams) (LoginAPI, ConnectionAPI, error) { 40 api, err := juju.NewAPIConnection(args) 41 if err != nil { 42 return nil, nil, errors.Trace(err) 43 } 44 return usermanager.NewClient(api), api, nil 45 }, 46 }) 47 } 48 49 // loginCommand changes the password for a user. 50 type loginCommand struct { 51 modelcmd.ControllerCommandBase 52 newLoginAPI func(juju.NewAPIConnectionParams) (LoginAPI, ConnectionAPI, error) 53 User string 54 } 55 56 // Info implements Command.Info. 57 func (c *loginCommand) Info() *cmd.Info { 58 return &cmd.Info{ 59 Name: "login", 60 Args: "[username]", 61 Purpose: "Logs a user in to a controller.", 62 Doc: loginDoc, 63 } 64 } 65 66 // Init implements Command.Init. 67 func (c *loginCommand) Init(args []string) error { 68 var err error 69 c.User, err = cmd.ZeroOrOneArgs(args) 70 if err != nil { 71 return errors.Trace(err) 72 } 73 return nil 74 } 75 76 // LoginAPI provides the API methods that the login command uses. 77 type LoginAPI interface { 78 Close() error 79 } 80 81 // ConnectionAPI provides relevant API methods off the underlying connection. 82 type ConnectionAPI interface { 83 AuthTag() names.Tag 84 ControllerAccess() string 85 } 86 87 // Run implements Command.Run. 88 func (c *loginCommand) Run(ctx *cmd.Context) error { 89 controllerName := c.ControllerName() 90 store := c.ClientStore() 91 accountDetails, err := store.AccountDetails(controllerName) 92 if err != nil && !errors.IsNotFound(err) { 93 return errors.Trace(err) 94 } 95 96 user := c.User 97 if user == "" && accountDetails == nil { 98 // The username has not been specified, and there 99 // is no current account. See if the user can log 100 // in with macaroons. 101 args, err := c.NewAPIConnectionParams( 102 store, controllerName, "", 103 &jujuclient.AccountDetails{}, 104 ) 105 if err != nil { 106 return errors.Trace(err) 107 } 108 api, conn, err := c.newLoginAPI(args) 109 if err == nil { 110 authTag := conn.AuthTag() 111 api.Close() 112 ctx.Infof("You are now logged in to %q as %q.", controllerName, authTag.Id()) 113 return nil 114 } 115 if !params.IsCodeNoCreds(err) { 116 return errors.Annotate(err, "creating API connection") 117 } 118 // CodeNoCreds was returned, which means that external 119 // users are not supported. Fall back to prompting the 120 // user for their username and password. 121 } 122 123 if user == "" { 124 // The username has not been specified, so prompt for it. 125 fmt.Fprint(ctx.Stderr, "username: ") 126 var err error 127 user, err = readLine(ctx.Stdin) 128 if err != nil { 129 return errors.Trace(err) 130 } 131 if user == "" { 132 return errors.Errorf("you must specify a username") 133 } 134 } 135 if !names.IsValidUserName(user) { 136 return errors.NotValidf("user name %q", user) 137 } 138 userTag := names.NewUserTag(user) 139 140 // Make sure that the client is not already logged in, 141 // or if it is, that it is logged in as the specified 142 // user. 143 if accountDetails != nil && accountDetails.User != userTag.Canonical() { 144 return errors.New(`already logged in 145 146 Run "juju logout" first before attempting to log in as a different user. 147 `) 148 } 149 150 // Log in without specifying a password in the account details. This 151 // will trigger macaroon-based authentication, which will prompt the 152 // user for their password. 153 accountDetails = &jujuclient.AccountDetails{ 154 User: userTag.Canonical(), 155 } 156 params, err := c.NewAPIConnectionParams(store, controllerName, "", accountDetails) 157 if err != nil { 158 return errors.Trace(err) 159 } 160 api, conn, err := c.newLoginAPI(params) 161 if err != nil { 162 return errors.Annotate(err, "creating API connection") 163 } 164 defer api.Close() 165 166 accountDetails.LastKnownAccess = conn.ControllerAccess() 167 if err := store.UpdateAccount(controllerName, *accountDetails); err != nil { 168 return errors.Annotate(err, "failed to record temporary credential") 169 } 170 ctx.Infof("You are now logged in to %q as %q.", controllerName, userTag.Canonical()) 171 return nil 172 }