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 = &timestamp
   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() }