-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathuser.go
244 lines (219 loc) · 6.31 KB
/
user.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
package state
import (
"fmt"
"time"
"github.com/juju/errors"
"github.com/juju/names"
"github.com/juju/utils"
"labix.org/v2/mgo"
"labix.org/v2/mgo/bson"
"labix.org/v2/mgo/txn"
)
func (st *State) checkUserExists(name string) (bool, error) {
var count int
var err error
if count, err = st.users.FindId(name).Count(); err != nil {
return false, err
}
return count > 0, nil
}
func (st *State) AddAdminUser(password string) (*User, error) {
return st.AddUser("admin", "", password, "")
}
// AddUser adds a user to the state.
func (st *State) AddUser(username, displayName, password, creator string) (*User, error) {
if !names.IsUser(username) {
return nil, errors.Errorf("invalid user name %q", username)
}
salt, err := utils.RandomSalt()
if err != nil {
return nil, err
}
timestamp := time.Now().Round(time.Second).UTC()
u := &User{
st: st,
doc: userDoc{
Name: username,
DisplayName: displayName,
PasswordHash: utils.UserPasswordHash(password, salt),
PasswordSalt: salt,
CreatedBy: creator,
DateCreated: timestamp,
},
}
ops := []txn.Op{{
C: st.users.Name,
Id: username,
Assert: txn.DocMissing,
Insert: &u.doc,
}}
err = st.runTransaction(ops)
if err == txn.ErrAborted {
err = errors.New("user already exists")
}
if err != nil {
return nil, errors.Trace(err)
}
return u, nil
}
// getUser fetches information about the user with the
// given name into the provided userDoc.
func (st *State) getUser(name string, udoc *userDoc) error {
err := st.users.Find(bson.D{{"_id", name}}).One(udoc)
if err == mgo.ErrNotFound {
err = errors.NotFoundf("user %q", name)
}
return err
}
// User returns the state user for the given name,
func (st *State) User(name string) (*User, error) {
u := &User{st: st}
if err := st.getUser(name, &u.doc); err != nil {
return nil, errors.Trace(err)
}
return u, nil
}
// User represents a juju client user.
type User struct {
st *State
doc userDoc
}
type userDoc struct {
Name string `bson:"_id_"`
DisplayName string
Deactivated bool // Removing users means they still exist, but are marked deactivated
PasswordHash string
PasswordSalt string
CreatedBy string
DateCreated time.Time
LastConnection time.Time
}
// Name returns the user name,
func (u *User) Name() string {
return u.doc.Name
}
// DisplayName returns the display name of the user.
func (u *User) DisplayName() string {
return u.doc.DisplayName
}
// CreatedBy returns the name of the user that created this user.
func (u *User) CreatedBy() string {
return u.doc.CreatedBy
}
// DateCreated returns when this user was created in UTC.
func (u *User) DateCreated() time.Time {
return u.doc.DateCreated.UTC()
}
// LastConnection returns when this user last connected through the API in UTC.
func (u *User) LastConnection() *time.Time {
result := u.doc.LastConnection
if result.IsZero() {
return nil
}
result = result.UTC()
return &result
}
func (u *User) UpdateLastConnection() error {
timestamp := time.Now().Round(time.Second).UTC()
ops := []txn.Op{{
C: u.st.users.Name,
Id: u.Name(),
Update: bson.D{{"$set", bson.D{{"lastconnection", timestamp}}}},
}}
if err := u.st.runTransaction(ops); err != nil {
return errors.Annotatef(err, "cannot update last connection timestamp for user %q", u.Name())
}
u.doc.LastConnection = timestamp
return nil
}
// Tag returns the Tag for the User.
func (u *User) Tag() names.Tag {
return names.NewUserTag(u.doc.Name)
}
// SetPassword sets the password associated with the user.
func (u *User) SetPassword(password string) error {
salt, err := utils.RandomSalt()
if err != nil {
return err
}
return u.SetPasswordHash(utils.UserPasswordHash(password, salt), salt)
}
// SetPasswordHash sets the password to the
// inverse of pwHash = utils.UserPasswordHash(pw, pwSalt).
// It can be used when we know only the hash
// of the password, but not the clear text.
func (u *User) SetPasswordHash(pwHash string, pwSalt string) error {
ops := []txn.Op{{
C: u.st.users.Name,
Id: u.Name(),
Update: bson.D{{"$set", bson.D{{"passwordhash", pwHash}, {"passwordsalt", pwSalt}}}},
}}
if err := u.st.runTransaction(ops); err != nil {
return fmt.Errorf("cannot set password of user %q: %v", u.Name(), err)
}
u.doc.PasswordHash = pwHash
u.doc.PasswordSalt = pwSalt
return nil
}
// PasswordValid returns whether the given password
// is valid for the user.
func (u *User) PasswordValid(password string) bool {
// If the user is deactivated, no point in carrying on
if u.IsDeactivated() {
return false
}
// Since these are potentially set by a User, we intentionally use the
// slower pbkdf2 style hashing. Also, we don't expect to have thousands
// of Users trying to log in at the same time (which we *do* expect of
// Unit and Machine agents.)
if u.doc.PasswordSalt != "" {
return utils.UserPasswordHash(password, u.doc.PasswordSalt) == u.doc.PasswordHash
}
// In Juju 1.16 and older, we did not set a Salt for the user password,
// so check if the password hash matches using CompatSalt. if it
// does, then set the password again so that we get a proper salt
if utils.UserPasswordHash(password, utils.CompatSalt) == u.doc.PasswordHash {
// This will set a new Salt for the password. We ignore if it
// fails because we will try again at the next request
logger.Debugf("User %s logged in with CompatSalt resetting password for new salt",
u.Name())
err := u.SetPassword(password)
if err != nil {
logger.Errorf("Cannot set resalted password for user %q", u.Name())
}
return true
}
return false
}
// Refresh refreshes information about the user
// from the state.
func (u *User) Refresh() error {
var udoc userDoc
if err := u.st.getUser(u.Name(), &udoc); err != nil {
return err
}
u.doc = udoc
return nil
}
func (u *User) Deactivate() error {
if u.doc.Name == AdminUser {
return errors.Unauthorizedf("Can't deactivate admin user")
}
ops := []txn.Op{{
C: u.st.users.Name,
Id: u.Name(),
Update: bson.D{{"$set", bson.D{{"deactivated", true}}}},
Assert: txn.DocExists,
}}
if err := u.st.runTransaction(ops); err != nil {
if err == txn.ErrAborted {
err = fmt.Errorf("user no longer exists")
}
return fmt.Errorf("cannot deactivate user %q: %v", u.Name(), err)
}
u.doc.Deactivated = true
return nil
}
func (u *User) IsDeactivated() bool {
return u.doc.Deactivated
}