github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/state/user.go (about) 1 // Copyright 2012-2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 // NOTE: the users that are being stored in the database here are only 5 // the local users, like "admin" or "bob" (@local). In the world 6 // where we have external user providers hooked up, there are no records 7 // in the databse for users that are authenticated elsewhere. 8 9 package state 10 11 import ( 12 "fmt" 13 "sort" 14 "time" 15 16 "github.com/juju/errors" 17 "github.com/juju/names" 18 "github.com/juju/utils" 19 "gopkg.in/mgo.v2" 20 "gopkg.in/mgo.v2/bson" 21 "gopkg.in/mgo.v2/txn" 22 ) 23 24 const ( 25 localUserProviderName = "local" 26 ) 27 28 func (st *State) checkUserExists(name string) (bool, error) { 29 users, closer := st.getCollection(usersC) 30 defer closer() 31 32 var count int 33 var err error 34 if count, err = users.FindId(name).Count(); err != nil { 35 return false, err 36 } 37 return count > 0, nil 38 } 39 40 // AddUser adds a user to the database. 41 func (st *State) AddUser(name, displayName, password, creator string) (*User, error) { 42 if !names.IsValidUserName(name) { 43 return nil, errors.Errorf("invalid user name %q", name) 44 } 45 salt, err := utils.RandomSalt() 46 if err != nil { 47 return nil, err 48 } 49 user := &User{ 50 st: st, 51 doc: userDoc{ 52 Name: name, 53 DisplayName: displayName, 54 PasswordHash: utils.UserPasswordHash(password, salt), 55 PasswordSalt: salt, 56 CreatedBy: creator, 57 DateCreated: nowToTheSecond(), 58 }, 59 } 60 ops := []txn.Op{{ 61 C: usersC, 62 Id: name, 63 Assert: txn.DocMissing, 64 Insert: &user.doc, 65 }} 66 err = st.runTransaction(ops) 67 if err == txn.ErrAborted { 68 err = errors.New("user already exists") 69 } 70 if err != nil { 71 return nil, errors.Trace(err) 72 } 73 return user, nil 74 } 75 76 func createInitialUserOp(st *State, user names.UserTag, password string) txn.Op { 77 doc := userDoc{ 78 Name: user.Name(), 79 DisplayName: user.Name(), 80 PasswordHash: password, 81 // Empty PasswordSalt means utils.CompatSalt 82 CreatedBy: user.Name(), 83 DateCreated: nowToTheSecond(), 84 } 85 return txn.Op{ 86 C: usersC, 87 Id: doc.Name, 88 Assert: txn.DocMissing, 89 Insert: &doc, 90 } 91 } 92 93 // getUser fetches information about the user with the 94 // given name into the provided userDoc. 95 func (st *State) getUser(name string, udoc *userDoc) error { 96 users, closer := st.getCollection(usersC) 97 defer closer() 98 99 err := users.Find(bson.D{{"_id", name}}).One(udoc) 100 if err == mgo.ErrNotFound { 101 err = errors.NotFoundf("user %q", name) 102 } 103 return err 104 } 105 106 // User returns the state User for the given name, 107 func (st *State) User(tag names.UserTag) (*User, error) { 108 if !tag.IsLocal() { 109 return nil, errors.NotFoundf("user %q", tag.Username()) 110 } 111 user := &User{st: st} 112 if err := st.getUser(tag.Name(), &user.doc); err != nil { 113 return nil, errors.Trace(err) 114 } 115 return user, nil 116 } 117 118 // User returns the state User for the given name, 119 func (st *State) AllUsers(includeDeactivated bool) ([]*User, error) { 120 var result []*User 121 122 users, closer := st.getCollection(usersC) 123 defer closer() 124 125 var query bson.D 126 if !includeDeactivated { 127 query = append(query, bson.DocElem{"deactivated", false}) 128 } 129 iter := users.Find(query).Iter() 130 defer iter.Close() 131 132 var doc userDoc 133 for iter.Next(&doc) { 134 result = append(result, &User{st: st, doc: doc}) 135 } 136 if err := iter.Err(); err != nil { 137 return nil, errors.Trace(err) 138 } 139 // Always return a predictable order, sort by Name. 140 sort.Sort(userList(result)) 141 return result, nil 142 } 143 144 // User represents a local user in the database. 145 type User struct { 146 st *State 147 doc userDoc 148 } 149 150 type userDoc struct { 151 Name string `bson:"_id"` 152 DisplayName string `bson:"displayname"` 153 // Removing users means they still exist, but are marked deactivated 154 Deactivated bool `bson:"deactivated"` 155 PasswordHash string `bson:"passwordhash"` 156 PasswordSalt string `bson:"passwordsalt"` 157 CreatedBy string `bson:"createdby"` 158 DateCreated time.Time `bson:"datecreated"` 159 LastLogin *time.Time `bson:"lastlogin"` 160 } 161 162 // String returns "<name>@local" where <name> is the Name of the user. 163 func (u *User) String() string { 164 return u.UserTag().Username() 165 } 166 167 // Name returns the User name. 168 func (u *User) Name() string { 169 return u.doc.Name 170 } 171 172 // DisplayName returns the display name of the User. 173 func (u *User) DisplayName() string { 174 return u.doc.DisplayName 175 } 176 177 // CreatedBy returns the name of the User that created this User. 178 func (u *User) CreatedBy() string { 179 return u.doc.CreatedBy 180 } 181 182 // DateCreated returns when this User was created in UTC. 183 func (u *User) DateCreated() time.Time { 184 return u.doc.DateCreated.UTC() 185 } 186 187 // Tag returns the Tag for the User. 188 func (u *User) Tag() names.Tag { 189 return u.UserTag() 190 } 191 192 // UserTag returns the Tag for the User. 193 func (u *User) UserTag() names.UserTag { 194 return names.NewLocalUserTag(u.doc.Name) 195 } 196 197 // LastLogin returns when this User last connected through the API in UTC. 198 // The resulting time will be nil if the user has never logged in. In the 199 // normal case, the LastLogin is the last time that the user connected through 200 // the API server. 201 func (u *User) LastLogin() *time.Time { 202 when := u.doc.LastLogin 203 if when == nil { 204 return nil 205 } 206 result := when.UTC() 207 return &result 208 } 209 210 // nowToTheSecond returns the current time in UTC to the nearest second. 211 // We use this for a time source that is not more precise than we can 212 // handle. When serializing time in and out of mongo, we lose enough 213 // precision that it's misleading to store any more than precision to 214 // the second. 215 // TODO(jcw4) time dependencies should be injectable, not just internal 216 // to package. 217 var nowToTheSecond = func() time.Time { return time.Now().Round(time.Second).UTC() } 218 219 // UpdateLastLogin sets the LastLogin time of the user to be now (to the 220 // nearest second). 221 func (u *User) UpdateLastLogin() error { 222 timestamp := nowToTheSecond() 223 ops := []txn.Op{{ 224 C: usersC, 225 Id: u.Name(), 226 Assert: txn.DocExists, 227 Update: bson.D{{"$set", bson.D{{"lastlogin", timestamp}}}}, 228 }} 229 if err := u.st.runTransaction(ops); err != nil { 230 return errors.Annotatef(err, "cannot update last login timestamp for user %q", u.Name()) 231 } 232 233 u.doc.LastLogin = ×tamp 234 return nil 235 } 236 237 // SetPassword sets the password associated with the User. 238 func (u *User) SetPassword(password string) error { 239 salt, err := utils.RandomSalt() 240 if err != nil { 241 return err 242 } 243 return u.SetPasswordHash(utils.UserPasswordHash(password, salt), salt) 244 } 245 246 // SetPasswordHash stores the hash and the salt of the password. 247 func (u *User) SetPasswordHash(pwHash string, pwSalt string) error { 248 ops := []txn.Op{{ 249 C: usersC, 250 Id: u.Name(), 251 Assert: txn.DocExists, 252 Update: bson.D{{"$set", bson.D{{"passwordhash", pwHash}, {"passwordsalt", pwSalt}}}}, 253 }} 254 if err := u.st.runTransaction(ops); err != nil { 255 return errors.Annotatef(err, "cannot set password of user %q", u.Name()) 256 } 257 u.doc.PasswordHash = pwHash 258 u.doc.PasswordSalt = pwSalt 259 return nil 260 } 261 262 // PasswordValid returns whether the given password is valid for the User. 263 func (u *User) PasswordValid(password string) bool { 264 // If the User is deactivated, no point in carrying on. Since any 265 // authentication checks are done very soon after the user is read 266 // from the database, there is a very small timeframe where an user 267 // could be disabled after it has been read but prior to being checked, 268 // but in practice, this isn't a problem. 269 if u.IsDisabled() { 270 return false 271 } 272 if u.doc.PasswordSalt != "" { 273 return utils.UserPasswordHash(password, u.doc.PasswordSalt) == u.doc.PasswordHash 274 } 275 // In Juju 1.16 and older, we did not set a Salt for the user password, 276 // so check if the password hash matches using CompatSalt. if it 277 // does, then set the password again so that we get a proper salt 278 if utils.UserPasswordHash(password, utils.CompatSalt) == u.doc.PasswordHash { 279 // This will set a new Salt for the password. We ignore if it 280 // fails because we will try again at the next request 281 logger.Debugf("User %s logged in with CompatSalt resetting password for new salt", 282 u.Name()) 283 err := u.SetPassword(password) 284 if err != nil { 285 logger.Errorf("Cannot set resalted password for user %q", u.Name()) 286 } 287 return true 288 } 289 return false 290 } 291 292 // Refresh refreshes information about the User from the state. 293 func (u *User) Refresh() error { 294 var udoc userDoc 295 if err := u.st.getUser(u.Name(), &udoc); err != nil { 296 return err 297 } 298 u.doc = udoc 299 return nil 300 } 301 302 // Disable deactivates the user. Disabled identities cannot log in. 303 func (u *User) Disable() error { 304 environment, err := u.st.StateServerEnvironment() 305 if err != nil { 306 return errors.Trace(err) 307 } 308 if u.doc.Name == environment.Owner().Name() { 309 return errors.Unauthorizedf("cannot disable state server environment owner") 310 } 311 return errors.Annotatef(u.setDeactivated(true), "cannot disable user %q", u.Name()) 312 } 313 314 // Enable reactivates the user, setting disabled to false. 315 func (u *User) Enable() error { 316 return errors.Annotatef(u.setDeactivated(false), "cannot enable user %q", u.Name()) 317 } 318 319 func (u *User) setDeactivated(value bool) error { 320 ops := []txn.Op{{ 321 C: usersC, 322 Id: u.Name(), 323 Assert: txn.DocExists, 324 Update: bson.D{{"$set", bson.D{{"deactivated", value}}}}, 325 }} 326 if err := u.st.runTransaction(ops); err != nil { 327 if err == txn.ErrAborted { 328 err = fmt.Errorf("user no longer exists") 329 } 330 return err 331 } 332 u.doc.Deactivated = value 333 return nil 334 } 335 336 // IsDisabled returns whether the user is currently enabled. 337 func (u *User) IsDisabled() bool { 338 // Yes, this is a cached value, but in practice the user object is 339 // never held around for a long time. 340 return u.doc.Deactivated 341 } 342 343 // userList type is used to provide the methods for sorting. 344 type userList []*User 345 346 func (u userList) Len() int { return len(u) } 347 func (u userList) Swap(i, j int) { u[i], u[j] = u[j], u[i] } 348 func (u userList) Less(i, j int) bool { return u[i].Name() < u[j].Name() }