<commit_before>// Copyright 2012-2014 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

// NOTE: the users that are being stored in the database here are only
// the local users, like "admin" or "bob" (@local).  In the  world
// where we have external user providers hooked up, there are no records
// in the databse for users that are authenticated elsewhere.

package state

import (
	"crypto/rand"
	"fmt"
	"sort"
	"strings"
	"time"

	"github.com/juju/errors"
	jujutxn "github.com/juju/txn"
	"github.com/juju/utils"
	"gopkg.in/juju/names.v2"
	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
	"gopkg.in/mgo.v2/txn"
)

func (st *State) checkUserExists(name string) (bool, error) {
	users, closer := st.getCollection(usersC)
	defer closer()

	var count int
	var err error
	if count, err = users.FindId(name).Count(); err != nil {
		return false, err
	}
	return count > 0, nil
}

// AddUser adds a user to the database.
func (st *State) AddUser(name, displayName, password, creator string) (*User, error) {
	return st.addUser(name, displayName, password, creator, nil)
}

// AddUserWithSecretKey adds the user with the specified name, and assigns it
// a randomly generated secret key. This secret key may be used for the user
// and controller to mutually authenticate one another, without without relying
// on TLS certificates.
//
// The new user will not have a password. A password must be set, clearing the
// secret key in the process, before the user can login normally.
func (st *State) AddUserWithSecretKey(name, displayName, creator string) (*User, error) {
	// Generate a random, 32-byte secret key. This can be used
	// to obtain the controller's (self-signed) CA certificate
	// and set the user's password.
	var secretKey [32]byte
	if _, err := rand.Read(secretKey[:]); err != nil {
		return nil, errors.Trace(err)
	}
	return st.addUser(name, displayName, "", creator, secretKey[:])
}

func (st *State) addUser(name, displayName, password, creator string, secretKey []byte) (*User, error) {
	if !names.IsValidUserName(name) {
		return nil, errors.Errorf("invalid user name %q", name)
	}
	nameToLower := strings.ToLower(name)

	user := &User{
		st: st,
		doc: userDoc{
			DocID:       nameToLower,
			Name:        name,
			DisplayName: displayName,
			SecretKey:   secretKey,
			CreatedBy:   creator,
			DateCreated: nowToTheSecond(),
		},
	}

	if password != "" {
		salt, err := utils.RandomSalt()
		if err != nil {
			return nil, err
		}
		user.doc.PasswordHash = utils.UserPasswordHash(password, salt)
		user.doc.PasswordSalt = salt
	}

	ops := []txn.Op{{
		C:      usersC,
		Id:     nameToLower,
		Assert: txn.DocMissing,
		Insert: &user.doc,
	}}
	err := st.runTransaction(ops)
	if err == txn.ErrAborted {
		err = errors.AlreadyExistsf("user")
	}
	if err != nil {
		return nil, errors.Trace(err)
	}
	return user, nil
}

// RemoveUser removes the user with the given name from the User collection as
// well as any modeluser entries in the ModelUser collection.
func (st *State) RemoveUser(user names.UserTag) error {

	name := strings.ToLower(user.Name())

	_, err := st.User(user)
	if errors.IsNotFound(err) {
		return errors.Trace(err)
	}
	// This builds the transaction operations we want to try for the User.
	buildTxn := func(attempt int) ([]txn.Op, error) {

		ops := []txn.Op{{
			Id:     name,
			C:      usersC,
			Assert: txn.DocExists,
			Remove: true,
		}}
		return ops, nil
	}
	// We remove the user first so that new modelusers cannot be added while we
	// delete them next. We also do it seperately so that we don't have
	// assertion issues if we try to remove a non-existent user.
	err = st.run(buildTxn)

	// If there is no User then we return with a user not found error.
	if err == jujutxn.ErrNoOperations {
		return errors.NewUserNotFound(err, "user not found")
	}

	// Get a raw connection to the ModelUser collection.
	modelUsers, userCloser := st.getRawCollection(modelUsersC)
	defer userCloser()

	// Build the transaction for removing all ModelUsers for the user.
	buildTxn = func(attempt int) ([]txn.Op, error) {
		var modelUserIDs []struct {
			ID string `bson:"_id"`
		}
		// The `_id` is munged in the ModelUser collection because it is a
		// *local* collection. Here we pierce the veil in the interest of speed
		// and simplicity. Rather than building up the `_id` from the name and
		// model UUID in a series of loops we simply return the _ids which we
		// need to remove. This was left inside this function instead of
		// creating an external helper to prevent confusion with the existing
		// RemoveModelUser function which has a different purpose: namely
		// unsharing a model.
		err := modelUsers.Find(bson.D{
			{"user", user.Canonical()}}).Select(bson.D{{"_id", 1}}).All(&modelUserIDs)
		if err != nil {
			return nil, errors.Trace(err)
		}
		var ops []txn.Op
		for _, entry := range modelUserIDs {
			ops = append(ops, txn.Op{
				C:      modelUsersC,
				Id:     entry.ID,
				Assert: txn.DocExists,
				Remove: true,
			})
		}
		return ops, nil
	}
	return st.run(buildTxn)
}

func createInitialUserOp(st *State, user names.UserTag, password, salt string) txn.Op {<commit_msg>Update buildTxn to remove all in one txn & verify<commit_after>// Copyright 2012-2014 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

// NOTE: the users that are being stored in the database here are only
// the local users, like "admin" or "bob" (@local).  In the  world
// where we have external user providers hooked up, there are no records
// in the databse for users that are authenticated elsewhere.

package state

import (
	"crypto/rand"
	"fmt"
	"sort"
	"strings"
	"time"

	"github.com/juju/errors"
	jujutxn "github.com/juju/txn"
	"github.com/juju/utils"
	"gopkg.in/juju/names.v2"
	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
	"gopkg.in/mgo.v2/txn"
)

func (st *State) checkUserExists(name string) (bool, error) {
	users, closer := st.getCollection(usersC)
	defer closer()

	var count int
	var err error
	if count, err = users.FindId(name).Count(); err != nil {
		return false, err
	}
	return count > 0, nil
}

// AddUser adds a user to the database.
func (st *State) AddUser(name, displayName, password, creator string) (*User, error) {
	return st.addUser(name, displayName, password, creator, nil)
}

// AddUserWithSecretKey adds the user with the specified name, and assigns it
// a randomly generated secret key. This secret key may be used for the user
// and controller to mutually authenticate one another, without without relying
// on TLS certificates.
//
// The new user will not have a password. A password must be set, clearing the
// secret key in the process, before the user can login normally.
func (st *State) AddUserWithSecretKey(name, displayName, creator string) (*User, error) {
	// Generate a random, 32-byte secret key. This can be used
	// to obtain the controller's (self-signed) CA certificate
	// and set the user's password.
	var secretKey [32]byte
	if _, err := rand.Read(secretKey[:]); err != nil {
		return nil, errors.Trace(err)
	}
	return st.addUser(name, displayName, "", creator, secretKey[:])
}

func (st *State) addUser(name, displayName, password, creator string, secretKey []byte) (*User, error) {
	if !names.IsValidUserName(name) {
		return nil, errors.Errorf("invalid user name %q", name)
	}
	nameToLower := strings.ToLower(name)

	user := &User{
		st: st,
		doc: userDoc{
			DocID:       nameToLower,
			Name:        name,
			DisplayName: displayName,
			SecretKey:   secretKey,
			CreatedBy:   creator,
			DateCreated: nowToTheSecond(),
		},
	}

	if password != "" {
		salt, err := utils.RandomSalt()
		if err != nil {
			return nil, err
		}
		user.doc.PasswordHash = utils.UserPasswordHash(password, salt)
		user.doc.PasswordSalt = salt
	}

	ops := []txn.Op{{
		C:      usersC,
		Id:     nameToLower,
		Assert: txn.DocMissing,
		Insert: &user.doc,
	}}
	err := st.runTransaction(ops)
	if err == txn.ErrAborted {
		err = errors.AlreadyExistsf("user")
	}
	if err != nil {
		return nil, errors.Trace(err)
	}
	return user, nil
}

// RemoveUser removes the user with the given name from the User collection as
// well as any modeluser entries in the ModelUser collection.
func (st *State) RemoveUser(tag names.UserTag) error {
	name := strings.ToLower(tag.Name())

	u, err := st.User(tag)
	if errors.IsNotFound(err) {
		return errors.Trace(err)
	}

	buildTxn := func(attempt int) ([]txn.Op, error) {
		var ops []txn.Op
		if attempt > 0 {
			// If it is not our first attempt, refresh the user.
			if userErr := u.Refresh(); userErr != nil {
				if errors.IsNotFound(userErr) {
					// If there is no user, see if anything was added
					// elsewhere.
					ops, err := st.removeUserOpsFromOtherCollections(tag)
					if err != nil {
						return nil, err
					}
					// If there ops, return them.
					if len(ops) != 0 {
						return ops, nil
					}
					// Otherwise return ErrNoOperations
					return nil, jujutxn.ErrNoOperations
				}
				// Or return the unexpected error.
				return nil, errors.Trace(userErr)
			}
		}
		// Otherwise this is our first attempt or our first attempt failed. So
		// build the slice of transaction ops for UserModels,
		// UserModelLastConnectionTime, and User.
		ops, err := st.removeUserOpsFromOtherCollections(tag)
		if err != nil {
			return nil, err
		}
		ops = append(ops, txn.Op{
			Id:     name,
			C:      usersC,
			Assert: txn.DocExists,
			Remove: true,
		})
		return ops, nil
	}
	if err = st.run(buildTxn); err == nil {
		// If there's no error rerun to ensure nothing was added while building
		// our original ops.
		return st.run(buildTxn)
	}
	// Or return the error
	return err
}

func (st *State) removeUserOpsFromOtherCollections(user names.UserTag) ([]txn.Op, error) {
	var ops []txn.Op
	ops, err := st.modelUserOps(user)
	if err != nil {
		return ops, errors.Trace(err)
	}
	muSerLastConOps, err := st.modelUserLastConOps(user)
	if err != nil {
		return nil, errors.Trace(err)
	}
	if len(muSerLastConOps) > 0 {
		ops = append(ops, muSerLastConOps...)
	}
	return ops, nil
}

func (st *State) modelUserOps(user names.UserTag) ([]txn.Op, error) {
	c, closer := st.getRawCollection(modelUsersC)
	defer closer()

	var (
		ids []struct {
			ID string `bson:"_id"`
		}
		ops []txn.Op
	)
	err := c.Find(bson.D{
		{"user", user.Canonical()}}).Select(bson.D{{"_id", 1}}).All(&ids)
	if err != nil {
		return nil, errors.Trace(err)
	}
	for _, entry := range ids {
		ops = append(ops, txn.Op{
			C:      modelUsersC,
			Id:     entry.ID,
			Assert: txn.DocExists,
			Remove: true,
		})
	}
	return ops, nil
}

func (st *State) modelUserLastConOps(user names.UserTag) ([]txn.Op, error) {
	c, closer := st.getRawCollection(modelUserLastConnectionC)
	defer closer()

	var (
		ids []struct {
			ID string `bson:"_id"`
		}
		ops []txn.Op
	)
	err := c.Find(bson.D{
		{"user", user.Canonical()}}).Select(bson.D{{"_id", 1}}).All(&ids)
	if err != nil {
		return nil, errors.Trace(err)
	}
	for _, entry := range ids {
		ops = append(ops, txn.Op{
			C:      modelUserLastConnectionC,
			Id:     entry.ID,
			Assert: txn.DocExists,
			Remove: true,
		})
	}
	return ops, nil
}

func createInitialUserOp(st *State, user names.UserTag, password, salt string) txn.Op {
{'fraction_non_alphanumeric': 0.12279451078239173, 'fraction_numerical': 0.003742648369274639, 'mean_word_length': 3.339907192575406, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2074464', 'n_tokens_mistral': 4132, 'n_tokens_neox': 3794, 'n_words': 1269}