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  }