github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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 "gopkg.in/juju/names.v2" 13 "gopkg.in/mgo.v2" 14 "gopkg.in/mgo.v2/bson" 15 "gopkg.in/mgo.v2/txn" 16 17 "github.com/juju/juju/permission" 18 ) 19 20 // modelUserLastConnectionDoc is updated by the apiserver whenever the user 21 // connects over the API. This update is not done using mgo.txn so the values 22 // could well change underneath a normal transaction and as such, it should 23 // NEVER appear in any transaction asserts. It is really informational only as 24 // far as everyone except the api server is concerned. 25 type modelUserLastConnectionDoc struct { 26 ID string `bson:"_id"` 27 ModelUUID string `bson:"model-uuid"` 28 UserName string `bson:"user"` 29 LastConnection time.Time `bson:"last-connection"` 30 } 31 32 // setModelAccess changes the user's access permissions on the model. 33 func (st *State) setModelAccess(access permission.Access, userGlobalKey, modelUUID string) error { 34 if err := permission.ValidateModelAccess(access); err != nil { 35 return errors.Trace(err) 36 } 37 op := updatePermissionOp(modelKey(modelUUID), userGlobalKey, access) 38 err := st.runTransactionFor(modelUUID, []txn.Op{op}) 39 if err == txn.ErrAborted { 40 return errors.NotFoundf("existing permissions") 41 } 42 return errors.Trace(err) 43 } 44 45 // LastModelConnection returns when this User last connected through the API 46 // in UTC. The resulting time will be nil if the user has never logged in. 47 func (st *State) LastModelConnection(user names.UserTag) (time.Time, error) { 48 lastConnections, closer := st.getRawCollection(modelUserLastConnectionC) 49 defer closer() 50 51 username := user.Canonical() 52 var lastConn modelUserLastConnectionDoc 53 err := lastConnections.FindId(st.docID(username)).Select(bson.D{{"last-connection", 1}}).One(&lastConn) 54 if err != nil { 55 if err == mgo.ErrNotFound { 56 err = errors.Wrap(err, NeverConnectedError(username)) 57 } 58 return time.Time{}, errors.Trace(err) 59 } 60 61 return lastConn.LastConnection.UTC(), nil 62 } 63 64 // NeverConnectedError is used to indicate that a user has never connected to 65 // an model. 66 type NeverConnectedError string 67 68 // Error returns the error string for a user who has never connected to an 69 // model. 70 func (e NeverConnectedError) Error() string { 71 return `never connected: "` + string(e) + `"` 72 } 73 74 // IsNeverConnectedError returns true if err is of type NeverConnectedError. 75 func IsNeverConnectedError(err error) bool { 76 _, ok := errors.Cause(err).(NeverConnectedError) 77 return ok 78 } 79 80 // UpdateLastModelConnection updates the last connection time of the model user. 81 func (st *State) UpdateLastModelConnection(user names.UserTag) error { 82 return st.updateLastModelConnection(user, st.NowToTheSecond()) 83 } 84 85 func (st *State) updateLastModelConnection(user names.UserTag, when time.Time) error { 86 lastConnections, closer := st.getCollection(modelUserLastConnectionC) 87 defer closer() 88 89 lastConnectionsW := lastConnections.Writeable() 90 91 // Update the safe mode of the underlying session to not require 92 // write majority, nor sync to disk. 93 session := lastConnectionsW.Underlying().Database.Session 94 session.SetSafe(&mgo.Safe{}) 95 96 lastConn := modelUserLastConnectionDoc{ 97 ID: st.docID(strings.ToLower(user.Canonical())), 98 ModelUUID: st.ModelUUID(), 99 UserName: user.Canonical(), 100 LastConnection: when, 101 } 102 _, err := lastConnectionsW.UpsertId(lastConn.ID, lastConn) 103 return errors.Trace(err) 104 } 105 106 // ModelUser a model userAccessDoc. 107 func (st *State) modelUser(modelUUID string, user names.UserTag) (userAccessDoc, error) { 108 modelUser := userAccessDoc{} 109 modelUsers, closer := st.getCollectionFor(modelUUID, modelUsersC) 110 defer closer() 111 112 username := strings.ToLower(user.Canonical()) 113 err := modelUsers.FindId(username).One(&modelUser) 114 if err == mgo.ErrNotFound { 115 return userAccessDoc{}, errors.NotFoundf("model user %q", username) 116 } 117 if err != nil { 118 return userAccessDoc{}, errors.Trace(err) 119 } 120 // DateCreated is inserted as UTC, but read out as local time. So we 121 // convert it back to UTC here. 122 modelUser.DateCreated = modelUser.DateCreated.UTC() 123 return modelUser, nil 124 } 125 126 func createModelUserOps(modelUUID string, user, createdBy names.UserTag, displayName string, dateCreated time.Time, access permission.Access) []txn.Op { 127 creatorname := createdBy.Canonical() 128 doc := &userAccessDoc{ 129 ID: userAccessID(user), 130 ObjectUUID: modelUUID, 131 UserName: user.Canonical(), 132 DisplayName: displayName, 133 CreatedBy: creatorname, 134 DateCreated: dateCreated, 135 } 136 137 ops := []txn.Op{ 138 createPermissionOp(modelKey(modelUUID), userGlobalKey(userAccessID(user)), access), 139 { 140 C: modelUsersC, 141 Id: userAccessID(user), 142 Assert: txn.DocMissing, 143 Insert: doc, 144 }, 145 } 146 return ops 147 } 148 149 func removeModelUserOps(modelUUID string, user names.UserTag) []txn.Op { 150 return []txn.Op{ 151 removePermissionOp(modelKey(modelUUID), userGlobalKey(userAccessID(user))), 152 { 153 C: modelUsersC, 154 Id: userAccessID(user), 155 Assert: txn.DocExists, 156 Remove: true, 157 }} 158 } 159 160 // removeModelUser removes a user from the database. 161 func (st *State) removeModelUser(user names.UserTag) error { 162 ops := removeModelUserOps(st.ModelUUID(), user) 163 err := st.runTransaction(ops) 164 if err == txn.ErrAborted { 165 err = errors.NewNotFound(nil, fmt.Sprintf("model user %q does not exist", user.Canonical())) 166 } 167 if err != nil { 168 return errors.Trace(err) 169 } 170 return nil 171 } 172 173 // UserModel contains information about an model that a 174 // user has access to. 175 type UserModel struct { 176 *Model 177 User names.UserTag 178 } 179 180 // LastConnection returns the last time the user has connected to the 181 // model. 182 func (e *UserModel) LastConnection() (time.Time, error) { 183 lastConnections, lastConnCloser := e.st.getRawCollection(modelUserLastConnectionC) 184 defer lastConnCloser() 185 186 lastConnDoc := modelUserLastConnectionDoc{} 187 id := ensureModelUUID(e.ModelTag().Id(), strings.ToLower(e.User.Canonical())) 188 err := lastConnections.FindId(id).Select(bson.D{{"last-connection", 1}}).One(&lastConnDoc) 189 if (err != nil && err != mgo.ErrNotFound) || lastConnDoc.LastConnection.IsZero() { 190 return time.Time{}, errors.Trace(NeverConnectedError(e.User.Canonical())) 191 } 192 193 return lastConnDoc.LastConnection, nil 194 } 195 196 // ModelsForUser returns a list of models that the user 197 // is able to access. 198 func (st *State) ModelsForUser(user names.UserTag) ([]*UserModel, error) { 199 // Since there are no groups at this stage, the simplest way to get all 200 // the models that a particular user can see is to look through the 201 // model user collection. A raw collection is required to support 202 // queries across multiple models. 203 modelUsers, userCloser := st.getRawCollection(modelUsersC) 204 defer userCloser() 205 206 var userSlice []userAccessDoc 207 err := modelUsers.Find(bson.D{{"user", user.Canonical()}}).Select(bson.D{{"object-uuid", 1}, {"_id", 1}}).All(&userSlice) 208 if err != nil { 209 return nil, err 210 } 211 212 var result []*UserModel 213 for _, doc := range userSlice { 214 modelTag := names.NewModelTag(doc.ObjectUUID) 215 env, err := st.GetModel(modelTag) 216 if err != nil { 217 return nil, errors.Trace(err) 218 } 219 220 result = append(result, &UserModel{Model: env, User: user}) 221 } 222 223 return result, nil 224 } 225 226 // IsControllerAdmin returns true if the user specified has Super User Access. 227 func (st *State) IsControllerAdmin(user names.UserTag) (bool, error) { 228 ua, err := st.UserAccess(user, st.ControllerTag()) 229 if errors.IsNotFound(err) { 230 return false, nil 231 } 232 if err != nil { 233 return false, errors.Trace(err) 234 } 235 return ua.Access == permission.SuperuserAccess, nil 236 }