github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/state/modeluser.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package state 5 6 import ( 7 "fmt" 8 "strings" 9 "time" 10 11 "github.com/juju/errors" 12 "github.com/juju/names" 13 "gopkg.in/mgo.v2" 14 "gopkg.in/mgo.v2/bson" 15 "gopkg.in/mgo.v2/txn" 16 ) 17 18 // ModelUser represents a user access to an model whereas the user 19 // could represent a remote user or a user across multiple models the 20 // model user always represents a single user for a single model. 21 // There should be no more than one ModelUser per model. 22 type ModelUser struct { 23 st *State 24 doc modelUserDoc 25 } 26 27 type modelUserDoc struct { 28 ID string `bson:"_id"` 29 ModelUUID string `bson:"model-uuid"` 30 UserName string `bson:"user"` 31 DisplayName string `bson:"displayname"` 32 CreatedBy string `bson:"createdby"` 33 DateCreated time.Time `bson:"datecreated"` 34 Access ModelAccess `bson:"access"` 35 } 36 37 // ModelAccess represents the level of access granted to a user on a model. 38 type ModelAccess string 39 40 const ( 41 // ModelUndefinedAccess is not a valid access type. It is the value 42 // unmarshaled when access is not defined by the document at all. 43 ModelUndefinedAccess ModelAccess = "" 44 45 // ModelReadAccess allows a user to read information about a model, without 46 // being able to make any changes. 47 ModelReadAccess ModelAccess = "read" 48 49 // ModelAdminAccess allows a user full control over the model. 50 ModelAdminAccess ModelAccess = "admin" 51 ) 52 53 // modelUserLastConnectionDoc is updated by the apiserver whenever the user 54 // connects over the API. This update is not done using mgo.txn so the values 55 // could well change underneath a normal transaction and as such, it should 56 // NEVER appear in any transaction asserts. It is really informational only as 57 // far as everyone except the api server is concerned. 58 type modelUserLastConnectionDoc struct { 59 ID string `bson:"_id"` 60 ModelUUID string `bson:"model-uuid"` 61 UserName string `bson:"user"` 62 LastConnection time.Time `bson:"last-connection"` 63 } 64 65 // ID returns the ID of the model user. 66 func (e *ModelUser) ID() string { 67 return e.doc.ID 68 } 69 70 // ModelTag returns the model tag of the model user. 71 func (e *ModelUser) ModelTag() names.ModelTag { 72 return names.NewModelTag(e.doc.ModelUUID) 73 } 74 75 // UserTag returns the tag for the model user. 76 func (e *ModelUser) UserTag() names.UserTag { 77 return names.NewUserTag(e.doc.UserName) 78 } 79 80 // UserName returns the user name of the model user. 81 func (e *ModelUser) UserName() string { 82 return e.doc.UserName 83 } 84 85 // DisplayName returns the display name of the model user. 86 func (e *ModelUser) DisplayName() string { 87 return e.doc.DisplayName 88 } 89 90 // CreatedBy returns the user who created the model user. 91 func (e *ModelUser) CreatedBy() string { 92 return e.doc.CreatedBy 93 } 94 95 // DateCreated returns the date the model user was created in UTC. 96 func (e *ModelUser) DateCreated() time.Time { 97 return e.doc.DateCreated.UTC() 98 } 99 100 // ReadOnly returns whether or not the user has write access or only 101 // read access to the model. 102 func (e *ModelUser) ReadOnly() bool { 103 // Fall back to read-only if access is undefined. 104 return e.doc.Access == ModelUndefinedAccess || e.doc.Access == ModelReadAccess 105 } 106 107 // Access returns the access permission that the user has to the model. 108 func (e *ModelUser) Access() ModelAccess { 109 return e.doc.Access 110 } 111 112 // SetAccess changes the user's access permissions on the model. 113 func (e *ModelUser) SetAccess(access ModelAccess) error { 114 switch access { 115 case ModelReadAccess, ModelAdminAccess: 116 default: 117 return errors.Errorf("invalid model access %q", access) 118 } 119 op := txn.Op{ 120 C: modelUsersC, 121 Id: e.st.docID(strings.ToLower(e.UserName())), 122 Assert: txn.DocExists, 123 Update: bson.D{{"$set", bson.D{{"access", access}}}}, 124 } 125 err := e.st.runTransaction([]txn.Op{op}) 126 return errors.Trace(err) 127 } 128 129 // LastConnection returns when this ModelUser last connected through the API 130 // in UTC. The resulting time will be nil if the user has never logged in. 131 func (e *ModelUser) LastConnection() (time.Time, error) { 132 lastConnections, closer := e.st.getRawCollection(modelUserLastConnectionC) 133 defer closer() 134 135 username := strings.ToLower(e.UserName()) 136 var lastConn modelUserLastConnectionDoc 137 err := lastConnections.FindId(e.st.docID(username)).Select(bson.D{{"last-connection", 1}}).One(&lastConn) 138 if err != nil { 139 if err == mgo.ErrNotFound { 140 err = errors.Wrap(err, NeverConnectedError(e.UserName())) 141 } 142 return time.Time{}, errors.Trace(err) 143 } 144 145 return lastConn.LastConnection.UTC(), nil 146 } 147 148 // NeverConnectedError is used to indicate that a user has never connected to 149 // an model. 150 type NeverConnectedError string 151 152 // Error returns the error string for a user who has never connected to an 153 // model. 154 func (e NeverConnectedError) Error() string { 155 return `never connected: "` + string(e) + `"` 156 } 157 158 // IsNeverConnectedError returns true if err is of type NeverConnectedError. 159 func IsNeverConnectedError(err error) bool { 160 _, ok := errors.Cause(err).(NeverConnectedError) 161 return ok 162 } 163 164 // UpdateLastConnection updates the last connection time of the model user. 165 func (e *ModelUser) UpdateLastConnection() error { 166 return e.updateLastConnection(nowToTheSecond()) 167 } 168 169 func (e *ModelUser) updateLastConnection(when time.Time) error { 170 lastConnections, closer := e.st.getCollection(modelUserLastConnectionC) 171 defer closer() 172 173 lastConnectionsW := lastConnections.Writeable() 174 175 // Update the safe mode of the underlying session to not require 176 // write majority, nor sync to disk. 177 session := lastConnectionsW.Underlying().Database.Session 178 session.SetSafe(&mgo.Safe{}) 179 180 lastConn := modelUserLastConnectionDoc{ 181 ID: e.st.docID(strings.ToLower(e.UserName())), 182 ModelUUID: e.ModelTag().Id(), 183 UserName: e.UserName(), 184 LastConnection: when, 185 } 186 _, err := lastConnectionsW.UpsertId(lastConn.ID, lastConn) 187 return errors.Trace(err) 188 } 189 190 // ModelUser returns the model user. 191 func (st *State) ModelUser(user names.UserTag) (*ModelUser, error) { 192 modelUser := &ModelUser{st: st} 193 modelUsers, closer := st.getCollection(modelUsersC) 194 defer closer() 195 196 username := strings.ToLower(user.Canonical()) 197 err := modelUsers.FindId(username).One(&modelUser.doc) 198 if err == mgo.ErrNotFound { 199 return nil, errors.NotFoundf("model user %q", user.Canonical()) 200 } 201 // DateCreated is inserted as UTC, but read out as local time. So we 202 // convert it back to UTC here. 203 modelUser.doc.DateCreated = modelUser.doc.DateCreated.UTC() 204 return modelUser, nil 205 } 206 207 // ModelUserSpec defines the attributes that can be set when adding a new 208 // model user. 209 type ModelUserSpec struct { 210 User names.UserTag 211 CreatedBy names.UserTag 212 DisplayName string 213 Access ModelAccess 214 } 215 216 // AddModelUser adds a new user to the database. 217 func (st *State) AddModelUser(spec ModelUserSpec) (*ModelUser, error) { 218 // Ensure local user exists in state before adding them as an model user. 219 if spec.User.IsLocal() { 220 localUser, err := st.User(spec.User) 221 if err != nil { 222 return nil, errors.Annotate(err, fmt.Sprintf("user %q does not exist locally", spec.User.Name())) 223 } 224 if spec.DisplayName == "" { 225 spec.DisplayName = localUser.DisplayName() 226 } 227 } 228 229 // Ensure local createdBy user exists. 230 if spec.CreatedBy.IsLocal() { 231 if _, err := st.User(spec.CreatedBy); err != nil { 232 return nil, errors.Annotatef(err, "createdBy user %q does not exist locally", spec.CreatedBy.Name()) 233 } 234 } 235 236 // Default to read access if not otherwise specified. 237 if spec.Access == ModelUndefinedAccess { 238 spec.Access = ModelReadAccess 239 } 240 241 modelUUID := st.ModelUUID() 242 op := createModelUserOp(modelUUID, spec.User, spec.CreatedBy, spec.DisplayName, nowToTheSecond(), spec.Access) 243 err := st.runTransaction([]txn.Op{op}) 244 if err == txn.ErrAborted { 245 err = errors.AlreadyExistsf("model user %q", spec.User.Canonical()) 246 } 247 if err != nil { 248 return nil, errors.Trace(err) 249 } 250 // Re-read from DB to get the multi-env updated values. 251 return st.ModelUser(spec.User) 252 } 253 254 // modelUserID returns the document id of the model user 255 func modelUserID(user names.UserTag) string { 256 username := user.Canonical() 257 return strings.ToLower(username) 258 } 259 260 func createModelUserOp(modelUUID string, user, createdBy names.UserTag, displayName string, dateCreated time.Time, access ModelAccess) txn.Op { 261 creatorname := createdBy.Canonical() 262 doc := &modelUserDoc{ 263 ID: modelUserID(user), 264 ModelUUID: modelUUID, 265 UserName: user.Canonical(), 266 DisplayName: displayName, 267 Access: access, 268 CreatedBy: creatorname, 269 DateCreated: dateCreated, 270 } 271 return txn.Op{ 272 C: modelUsersC, 273 Id: modelUserID(user), 274 Assert: txn.DocMissing, 275 Insert: doc, 276 } 277 } 278 279 // RemoveModelUser removes a user from the database. 280 func (st *State) RemoveModelUser(user names.UserTag) error { 281 ops := []txn.Op{{ 282 C: modelUsersC, 283 Id: modelUserID(user), 284 Assert: txn.DocExists, 285 Remove: true, 286 }} 287 err := st.runTransaction(ops) 288 if err == txn.ErrAborted { 289 err = errors.NewNotFound(nil, fmt.Sprintf("model user %q does not exist", user.Canonical())) 290 } 291 if err != nil { 292 return errors.Trace(err) 293 } 294 return nil 295 } 296 297 // UserModel contains information about an model that a 298 // user has access to. 299 type UserModel struct { 300 *Model 301 User names.UserTag 302 } 303 304 // LastConnection returns the last time the user has connected to the 305 // model. 306 func (e *UserModel) LastConnection() (time.Time, error) { 307 lastConnections, lastConnCloser := e.st.getRawCollection(modelUserLastConnectionC) 308 defer lastConnCloser() 309 310 lastConnDoc := modelUserLastConnectionDoc{} 311 id := ensureModelUUID(e.ModelTag().Id(), strings.ToLower(e.User.Canonical())) 312 err := lastConnections.FindId(id).Select(bson.D{{"last-connection", 1}}).One(&lastConnDoc) 313 if (err != nil && err != mgo.ErrNotFound) || lastConnDoc.LastConnection.IsZero() { 314 return time.Time{}, errors.Trace(NeverConnectedError(e.User.Canonical())) 315 } 316 317 return lastConnDoc.LastConnection, nil 318 } 319 320 // ModelsForUser returns a list of models that the user 321 // is able to access. 322 func (st *State) ModelsForUser(user names.UserTag) ([]*UserModel, error) { 323 // Since there are no groups at this stage, the simplest way to get all 324 // the models that a particular user can see is to look through the 325 // model user collection. A raw collection is required to support 326 // queries across multiple models. 327 modelUsers, userCloser := st.getRawCollection(modelUsersC) 328 defer userCloser() 329 330 // TODO: consider adding an index to the modelUsers collection on the username. 331 var userSlice []modelUserDoc 332 err := modelUsers.Find(bson.D{{"user", user.Canonical()}}).Select(bson.D{{"model-uuid", 1}, {"_id", 1}}).All(&userSlice) 333 if err != nil { 334 return nil, err 335 } 336 337 var result []*UserModel 338 for _, doc := range userSlice { 339 modelTag := names.NewModelTag(doc.ModelUUID) 340 env, err := st.GetModel(modelTag) 341 if err != nil { 342 return nil, errors.Trace(err) 343 } 344 345 result = append(result, &UserModel{Model: env, User: user}) 346 } 347 348 return result, nil 349 } 350 351 // IsControllerAdministrator returns true if the user specified has access to the 352 // controller model (the system model). 353 func (st *State) IsControllerAdministrator(user names.UserTag) (bool, error) { 354 ssinfo, err := st.ControllerInfo() 355 if err != nil { 356 return false, errors.Annotate(err, "could not get controller info") 357 } 358 359 serverUUID := ssinfo.ModelTag.Id() 360 361 modelUsers, userCloser := st.getRawCollection(modelUsersC) 362 defer userCloser() 363 364 count, err := modelUsers.Find(bson.D{ 365 {"model-uuid", serverUUID}, 366 {"user", user.Canonical()}, 367 {"access", ModelAdminAccess}, 368 }).Count() 369 if err != nil { 370 return false, errors.Trace(err) 371 } 372 return count == 1, nil 373 }