~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/cmd/juju/user/login.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
        "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
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: enable-user
 
30
          disable-user
 
31
          logout
 
32
 
 
33
`
 
34
 
 
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)
 
40
                        if err != nil {
 
41
                                return nil, errors.Trace(err)
 
42
                        }
 
43
                        return usermanager.NewClient(api), nil
 
44
                },
 
45
        })
 
46
}
 
47
 
 
48
// loginCommand changes the password for a user.
 
49
type loginCommand struct {
 
50
        modelcmd.ControllerCommandBase
 
51
        newLoginAPI func(juju.NewAPIConnectionParams) (LoginAPI, error)
 
52
        User        string
 
53
}
 
54
 
 
55
// Info implements Command.Info.
 
56
func (c *loginCommand) Info() *cmd.Info {
 
57
        return &cmd.Info{
 
58
                Name:    "login",
 
59
                Args:    "[username]",
 
60
                Purpose: "Logs a user in to a controller.",
 
61
                Doc:     loginDoc,
 
62
        }
 
63
}
 
64
 
 
65
// Init implements Command.Init.
 
66
func (c *loginCommand) Init(args []string) error {
 
67
        var err error
 
68
        c.User, err = cmd.ZeroOrOneArgs(args)
 
69
        if err != nil {
 
70
                return errors.Trace(err)
 
71
        }
 
72
        return nil
 
73
}
 
74
 
 
75
// LoginAPI provides the API methods that the login command uses.
 
76
type LoginAPI interface {
 
77
        CreateLocalLoginMacaroon(names.UserTag) (*macaroon.Macaroon, error)
 
78
        Close() error
 
79
}
 
80
 
 
81
// Run implements Command.Run.
 
82
func (c *loginCommand) Run(ctx *cmd.Context) error {
 
83
        controllerName := c.ControllerName()
 
84
        store := c.ClientStore()
 
85
 
 
86
        user := c.User
 
87
        if user == "" {
 
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: ")
 
92
                var err error
 
93
                user, err = readLine(ctx.Stdin)
 
94
                if err != nil {
 
95
                        return errors.Trace(err)
 
96
                }
 
97
                if user == "" {
 
98
                        return errors.Errorf("you must specify a username")
 
99
                }
 
100
        }
 
101
        if !names.IsValidUserName(user) {
 
102
                return errors.NotValidf("user name %q", user)
 
103
        }
 
104
        userTag := names.NewUserTag(user)
 
105
 
 
106
        // Make sure that the client is not already logged in,
 
107
        // or if it is, that it is logged in as the specified
 
108
        // user.
 
109
        accountDetails, err := store.AccountDetails(controllerName)
 
110
        if err != nil && !errors.IsNotFound(err) {
 
111
                return errors.Trace(err)
 
112
        }
 
113
        if accountDetails != nil && accountDetails.User != userTag.Canonical() {
 
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
        // 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)
 
123
        if err != nil {
 
124
                return errors.Trace(err)
 
125
        }
 
126
        accountDetails = &jujuclient.AccountDetails{
 
127
                User:     userTag.Canonical(),
 
128
                Password: password,
 
129
        }
 
130
        params, err := c.NewAPIConnectionParams(store, controllerName, "", accountDetails)
 
131
        if err != nil {
 
132
                return errors.Trace(err)
 
133
        }
 
134
        api, err := c.newLoginAPI(params)
 
135
        if err != nil {
 
136
                return errors.Annotate(err, "creating API connection")
 
137
        }
 
138
        defer api.Close()
 
139
 
 
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)
 
144
        if err != nil {
 
145
                return errors.Annotate(err, "failed to create a temporary credential")
 
146
        }
 
147
        macaroonJSON, err := macaroon.MarshalJSON()
 
148
        if err != nil {
 
149
                return errors.Annotate(err, "marshalling temporary credential to JSON")
 
150
        }
 
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")
 
155
        }
 
156
        ctx.Infof("You are now logged in to %q as %q.", controllerName, userTag.Canonical())
 
157
        return nil
 
158
}