1
// Copyright 2016 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
10
"github.com/juju/errors"
11
"gopkg.in/juju/names.v2"
12
"gopkg.in/macaroon.v1"
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"
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.
35
// NewLoginCommand returns a new cmd.Command to handle "juju login".
36
func NewLoginCommand() cmd.Command {
37
return modelcmd.WrapController(&loginCommand{
38
newLoginAPI: func(args juju.NewAPIConnectionParams) (LoginAPI, error) {
39
api, err := juju.NewAPIConnection(args)
41
return nil, errors.Trace(err)
43
return usermanager.NewClient(api), nil
48
// loginCommand changes the password for a user.
49
type loginCommand struct {
50
modelcmd.ControllerCommandBase
51
newLoginAPI func(juju.NewAPIConnectionParams) (LoginAPI, error)
55
// Info implements Command.Info.
56
func (c *loginCommand) Info() *cmd.Info {
60
Purpose: "Logs a user in to a controller.",
65
// Init implements Command.Init.
66
func (c *loginCommand) Init(args []string) error {
68
c.User, err = cmd.ZeroOrOneArgs(args)
70
return errors.Trace(err)
75
// LoginAPI provides the API methods that the login command uses.
76
type LoginAPI interface {
77
CreateLocalLoginMacaroon(names.UserTag) (*macaroon.Macaroon, error)
81
// Run implements Command.Run.
82
func (c *loginCommand) Run(ctx *cmd.Context) error {
83
controllerName := c.ControllerName()
84
store := c.ClientStore()
88
// TODO(rog) Try macaroon login first before
89
// falling back to prompting for username.
90
// The username has not been specified, so prompt for it.
91
fmt.Fprint(ctx.Stderr, "username: ")
93
user, err = readLine(ctx.Stdin)
95
return errors.Trace(err)
98
return errors.Errorf("you must specify a username")
101
if !names.IsValidUserName(user) {
102
return errors.NotValidf("user name %q", user)
104
userTag := names.NewUserTag(user)
106
// Make sure that the client is not already logged in,
107
// or if it is, that it is logged in as the specified
109
accountDetails, err := store.AccountDetails(controllerName)
110
if err != nil && !errors.IsNotFound(err) {
111
return errors.Trace(err)
113
if accountDetails != nil && accountDetails.User != userTag.Canonical() {
114
return errors.New(`already logged in
116
Run "juju logout" first before attempting to log in as a different user.
119
// Read password from the terminal, and attempt to log in using that.
120
fmt.Fprint(ctx.Stderr, "password: ")
121
password, err := readPassword(ctx.Stdin)
122
fmt.Fprintln(ctx.Stderr)
124
return errors.Trace(err)
126
accountDetails = &jujuclient.AccountDetails{
127
User: userTag.Canonical(),
130
params, err := c.NewAPIConnectionParams(store, controllerName, "", accountDetails)
132
return errors.Trace(err)
134
api, err := c.newLoginAPI(params)
136
return errors.Annotate(err, "creating API connection")
140
// Create a new local login macaroon, and update the account details
141
// in the client store, removing the recorded password (if any) and
142
// storing the macaroon.
143
macaroon, err := api.CreateLocalLoginMacaroon(userTag)
145
return errors.Annotate(err, "failed to create a temporary credential")
147
macaroonJSON, err := macaroon.MarshalJSON()
149
return errors.Annotate(err, "marshalling temporary credential to JSON")
151
accountDetails.Password = ""
152
accountDetails.Macaroon = string(macaroonJSON)
153
if err := store.UpdateAccount(controllerName, *accountDetails); err != nil {
154
return errors.Annotate(err, "failed to record temporary credential")
156
ctx.Infof("You are now logged in to %q as %q.", controllerName, userTag.Canonical())