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  }