// AddEnvironmentUser adds a new user to the database. func (st *State) AddEnvironmentUser(user, createdBy names.UserTag, displayName string) (*EnvironmentUser, error) { // Ensure local user exists in state before adding them as an environment user. if user.IsLocal() { localUser, err := st.User(user) if err != nil { return nil, errors.Annotate(err, fmt.Sprintf("user %q does not exist locally", user.Name())) } if displayName == "" { displayName = localUser.DisplayName() } } // Ensure local createdBy user exists. if createdBy.IsLocal() { if _, err := st.User(createdBy); err != nil { return nil, errors.Annotate(err, fmt.Sprintf("createdBy user %q does not exist locally", createdBy.Name())) } } envuuid := st.EnvironUUID() op := createEnvUserOp(envuuid, user, createdBy, displayName) err := st.runTransaction([]txn.Op{op}) if err == txn.ErrAborted { err = errors.AlreadyExistsf("environment user %q", user.Canonical()) } if err != nil { return nil, errors.Trace(err) } // Re-read from DB to get the multi-env updated values. return st.EnvironmentUser(user) }
// CreateLocalLoginMacaroon creates a time-limited macaroon for a local user // to log into the controller with. The macaroon will be valid for use with // UserAuthenticator.Authenticate until the time limit expires, or the Juju // controller agent restarts. // // NOTE(axw) this method will generate a key for a previously unseen user, // and store it in the bakery.Service's storage. Callers should first ensure // the user is valid before calling this, to avoid filling storage with keys // for invalid users. func (u *UserAuthenticator) CreateLocalLoginMacaroon(tag names.UserTag) (*macaroon.Macaroon, error) { expiryTime := u.Clock.Now().Add(localLoginExpiryTime) // Ensure that the private key that we generate and store will be // removed from storage once the expiry time has elapsed. bakeryService, err := u.Service.ExpireStorageAt(expiryTime) if err != nil { return nil, errors.Trace(err) } // We create the macaroon with a random ID and random root key, which // enables multiple clients to login as the same user and obtain separate // macaroons without having them use the same root key. m, err := bakeryService.NewMacaroon("", nil, []checkers.Caveat{ // The macaroon may only be used to log in as the user // specified by the tag passed to CreateLocalUserMacaroon. checkers.DeclaredCaveat(usernameKey, tag.Canonical()), }) if err != nil { return nil, errors.Annotate(err, "cannot create macaroon") } if err := addMacaroonTimeBeforeCaveat(bakeryService, m, expiryTime); err != nil { return nil, errors.Trace(err) } return m, nil }
// ModelsForUser returns a list of models that the user // is able to access. func (st *State) ModelsForUser(user names.UserTag) ([]*UserModel, error) { // Since there are no groups at this stage, the simplest way to get all // the models that a particular user can see is to look through the // model user collection. A raw collection is required to support // queries across multiple models. modelUsers, userCloser := st.getRawCollection(modelUsersC) defer userCloser() // TODO: consider adding an index to the modelUsers collection on the username. var userSlice []modelUserDoc err := modelUsers.Find(bson.D{{"user", user.Canonical()}}).Select(bson.D{{"model-uuid", 1}, {"_id", 1}}).All(&userSlice) if err != nil { return nil, err } var result []*UserModel for _, doc := range userSlice { modelTag := names.NewModelTag(doc.ModelUUID) env, err := st.GetModel(modelTag) if err != nil { return nil, errors.Trace(err) } result = append(result, &UserModel{Model: env, User: user}) } return result, nil }
// EnvironmentsForUser returns a list of enviroments that the user // is able to access. func (st *State) EnvironmentsForUser(user names.UserTag) ([]*UserEnvironment, error) { // Since there are no groups at this stage, the simplest way to get all // the environments that a particular user can see is to look through the // environment user collection. A raw collection is required to support // queries across multiple environments. envUsers, userCloser := st.getRawCollection(envUsersC) defer userCloser() // TODO: consider adding an index to the envUsers collection on the username. var userSlice []envUserDoc err := envUsers.Find(bson.D{{"user", user.Canonical()}}).Select(bson.D{{"env-uuid", 1}, {"_id", 1}}).All(&userSlice) if err != nil { return nil, err } var result []*UserEnvironment for _, doc := range userSlice { envTag := names.NewEnvironTag(doc.EnvUUID) env, err := st.GetEnvironment(envTag) if err != nil { return nil, errors.Trace(err) } result = append(result, &UserEnvironment{Environment: env, User: user}) } return result, nil }
func fakeLocalLoginMacaroon(tag names.UserTag) *macaroon.Macaroon { mac, err := macaroon.New([]byte("abcdefghijklmnopqrstuvwx"), tag.Canonical(), "juju") if err != nil { panic(err) } return mac }
// NewModel creates a new model with its own UUID and // prepares it for use. Model and State instances for the new // model are returned. // // The controller model's UUID is attached to the new // model's document. Having the server UUIDs stored with each // model document means that we have a way to represent external // models, perhaps for future use around cross model // relations. func (st *State) NewModel(cfg *config.Config, owner names.UserTag) (_ *Model, _ *State, err error) { if owner.IsLocal() { if _, err := st.User(owner); err != nil { return nil, nil, errors.Annotate(err, "cannot create model") } } ssEnv, err := st.ControllerModel() if err != nil { return nil, nil, errors.Annotate(err, "could not load controller model") } uuid := cfg.UUID() newState, err := st.ForModel(names.NewModelTag(uuid)) if err != nil { return nil, nil, errors.Annotate(err, "could not create state for new model") } defer func() { if err != nil { newState.Close() } }() ops, err := newState.envSetupOps(cfg, uuid, ssEnv.UUID(), owner) if err != nil { return nil, nil, errors.Annotate(err, "failed to create new model") } err = newState.runTransaction(ops) if err == txn.ErrAborted { // We have a unique key restriction on the "owner" and "name" fields, // which will cause the insert to fail if there is another record with // the same "owner" and "name" in the collection. If the txn is // aborted, check if it is due to the unique key restriction. models, closer := st.getCollection(modelsC) defer closer() envCount, countErr := models.Find(bson.D{ {"owner", owner.Canonical()}, {"name", cfg.Name()}}, ).Count() if countErr != nil { err = errors.Trace(countErr) } else if envCount > 0 { err = errors.AlreadyExistsf("model %q for %s", cfg.Name(), owner.Canonical()) } else { err = errors.New("model already exists") } } if err != nil { return nil, nil, errors.Trace(err) } newEnv, err := newState.Model() if err != nil { return nil, nil, errors.Trace(err) } return newEnv, newState, nil }
// createUniqueOwnerModelNameOp returns the operation needed to create // an usermodelnameC document with the given owner and model name. func createUniqueOwnerModelNameOp(owner names.UserTag, envName string) txn.Op { return txn.Op{ C: usermodelnameC, Id: userModelNameIndex(owner.Canonical(), envName), Assert: txn.DocMissing, Insert: bson.M{}, } }
// User returns the state User for the given name. func (st *State) User(tag names.UserTag) (*User, error) { if !tag.IsLocal() { return nil, errors.NotFoundf("user %q", tag.Canonical()) } user := &User{st: st} if err := st.getUser(tag.Name(), &user.doc); err != nil { return nil, errors.Trace(err) } return user, nil }
// authCheck checks if the user is acting on their own behalf, or if they // are an administrator acting on behalf of another user. func (m *ModelManagerAPI) authCheck(user names.UserTag) error { if m.isAdmin { logger.Tracef("%q is a controller admin", m.apiUser.Canonical()) return nil } // We can't just compare the UserTags themselves as the provider part // may be unset, and gets replaced with 'local'. We must compare against // the Canonical value of the user tag. if m.apiUser.Canonical() == user.Canonical() { return nil } return common.ErrPerm }
// ModelUser returns the model user. func (st *State) ModelUser(user names.UserTag) (*ModelUser, error) { modelUser := &ModelUser{st: st} modelUsers, closer := st.getCollection(modelUsersC) defer closer() username := strings.ToLower(user.Canonical()) err := modelUsers.FindId(username).One(&modelUser.doc) if err == mgo.ErrNotFound { return nil, errors.NotFoundf("model user %q", user.Canonical()) } // DateCreated is inserted as UTC, but read out as local time. So we // convert it back to UTC here. modelUser.doc.DateCreated = modelUser.doc.DateCreated.UTC() return modelUser, nil }
// createModelOp returns the operation needed to create // an model document with the given name and UUID. func createModelOp(st *State, owner names.UserTag, name, uuid, server string) txn.Op { doc := &modelDoc{ UUID: uuid, Name: name, Life: Alive, Owner: owner.Canonical(), ServerUUID: server, } return txn.Op{ C: modelsC, Id: uuid, Assert: txn.DocMissing, Insert: doc, } }
// EnvironmentUser returns the environment user. func (st *State) EnvironmentUser(user names.UserTag) (*EnvironmentUser, error) { envUser := &EnvironmentUser{st: st} envUsers, closer := st.getCollection(envUsersC) defer closer() username := strings.ToLower(user.Canonical()) err := envUsers.FindId(username).One(&envUser.doc) if err == mgo.ErrNotFound { return nil, errors.NotFoundf("environment user %q", user.Canonical()) } // DateCreated is inserted as UTC, but read out as local time. So we // convert it back to UTC here. envUser.doc.DateCreated = envUser.doc.DateCreated.UTC() return envUser, nil }
// RemoveModelUser removes a user from the database. func (st *State) RemoveModelUser(user names.UserTag) error { ops := []txn.Op{{ C: modelUsersC, Id: modelUserID(user), Assert: txn.DocExists, Remove: true, }} err := st.runTransaction(ops) if err == txn.ErrAborted { err = errors.NewNotFound(err, fmt.Sprintf("env user %q does not exist", user.Canonical())) } if err != nil { return errors.Trace(err) } return nil }
func createEnvUserOp(envuuid string, user, createdBy names.UserTag, displayName string) txn.Op { creatorname := createdBy.Canonical() doc := &envUserDoc{ ID: envUserID(user), EnvUUID: envuuid, UserName: user.Canonical(), DisplayName: displayName, CreatedBy: creatorname, DateCreated: nowToTheSecond(), } return txn.Op{ C: envUsersC, Id: envUserID(user), Assert: txn.DocMissing, Insert: doc, } }
func (u *UserAuthenticator) authenticateMacaroons( entityFinder EntityFinder, tag names.UserTag, req params.LoginRequest, ) (state.Entity, error) { // Check for a valid request macaroon. assert := map[string]string{usernameKey: tag.Canonical()} _, err := u.Service.CheckAny(req.Macaroons, assert, checkers.New(checkers.TimeBefore)) if err != nil { return nil, errors.Trace(err) } entity, err := entityFinder.FindEntity(tag) if errors.IsNotFound(err) { return nil, errors.Trace(common.ErrBadCreds) } else if err != nil { return nil, errors.Trace(err) } return entity, nil }
func createModelUserOp(modelUUID string, user, createdBy names.UserTag, displayName string, readOnly bool) txn.Op { creatorname := createdBy.Canonical() doc := &modelUserDoc{ ID: modelUserID(user), ModelUUID: modelUUID, UserName: user.Canonical(), DisplayName: displayName, ReadOnly: readOnly, CreatedBy: creatorname, DateCreated: nowToTheSecond(), } return txn.Op{ C: modelUsersC, Id: modelUserID(user), Assert: txn.DocMissing, Insert: doc, } }
func createModelUserOp(modelUUID string, user, createdBy names.UserTag, displayName string, dateCreated time.Time, access ModelAccess) txn.Op { creatorname := createdBy.Canonical() doc := &modelUserDoc{ ID: modelUserID(user), ModelUUID: modelUUID, UserName: user.Canonical(), DisplayName: displayName, Access: access, CreatedBy: creatorname, DateCreated: dateCreated, } return txn.Op{ C: modelUsersC, Id: modelUserID(user), Assert: txn.DocMissing, Insert: doc, } }
// IsControllerAdministrator returns true if the user specified has access to the // state server model (the system model). func (st *State) IsControllerAdministrator(user names.UserTag) (bool, error) { ssinfo, err := st.StateServerInfo() if err != nil { return false, errors.Annotate(err, "could not get state server info") } serverUUID := ssinfo.ModelTag.Id() modelUsers, userCloser := st.getRawCollection(modelUsersC) defer userCloser() count, err := modelUsers.Find(bson.D{ {"model-uuid", serverUUID}, {"user", user.Canonical()}, }).Count() if err != nil { return false, errors.Trace(err) } return count == 1, nil }
// CreateLocalLoginMacaroon creates a time-limited macaroon for a local user // to log into the controller with. The macaroon will be valid for use with // UserAuthenticator.Authenticate until the time limit expires, or the Juju // controller agent restarts. // // NOTE(axw) this method will generate a key for a previously unseen user, // and store it in the bakery.Service's storage, which is currently in-memory. // Callers should first ensure the user is valid before calling this, to avoid // filling memory with keys for invalid users. func (u *UserAuthenticator) CreateLocalLoginMacaroon(tag names.UserTag) (*macaroon.Macaroon, error) { // We create the macaroon with a random ID and random root key, which // enables multiple clients to login as the same user and obtain separate // macaroons without having them use the same root key. // // TODO(axw) check with rogpeppe about this. bakery.Service doesn't // currently garbage collect, so this will grow until the controller // agent restarts. m, err := u.Service.NewMacaroon("", nil, []checkers.Caveat{ // The macaroon may only be used to log in as the user // specified by the tag passed to CreateLocalUserMacaroon. checkers.DeclaredCaveat(usernameKey, tag.Canonical()), }) if err != nil { return nil, errors.Annotate(err, "cannot create macaroon") } if err := addMacaroonTimeBeforeCaveat(u.Service, m, localLoginExpiryTime); err != nil { return nil, errors.Trace(err) } return m, nil }
// authCheck checks if the user is acting on their own behalf, or if they // are an administrator acting on behalf of another user. func (em *EnvironmentManagerAPI) authCheck(user names.UserTag) error { // Since we know this is a user tag (because AuthClient is true), // we just do the type assertion to the UserTag. apiUser, _ := em.authorizer.GetAuthTag().(names.UserTag) isAdmin, err := em.state.IsSystemAdministrator(apiUser) if err != nil { return errors.Trace(err) } if isAdmin { logger.Tracef("%q is a system admin", apiUser.Canonical()) return nil } // We can't just compare the UserTags themselves as the provider part // may be unset, and gets replaced with 'local'. We must compare against // the Username of the user tag. if apiUser.Canonical() == user.Canonical() { return nil } return common.ErrPerm }
func (st *mockState) IsControllerAdministrator(user names.UserTag) (bool, error) { st.MethodCall(st, "IsControllerAdministrator", user) return user.Canonical() == "admin@local", st.NextErr() }
func (s *modelManagerSuite) assertNewUser(c *gc.C, modelUser *state.ModelUser, userTag, creatorTag names.UserTag) { c.Assert(modelUser.UserTag(), gc.Equals, userTag) c.Assert(modelUser.CreatedBy(), gc.Equals, creatorTag.Canonical()) _, err := modelUser.LastConnection() c.Assert(err, jc.Satisfies, state.IsNeverConnectedError) }
// modelUserID returns the document id of the model user func modelUserID(user names.UserTag) string { username := user.Canonical() return strings.ToLower(username) }