// 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.Username()}}).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, LastConnection: doc.LastConnection}) } 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 }
// 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 }
// 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{}, } }
// createUniqueOwnerEnvNameOp returns the operation needed to create // an userenvnameC document with the given owner and environment name. func createUniqueOwnerEnvNameOp(owner names.UserTag, envName string) txn.Op { return txn.Op{ C: userenvnameC, Id: userEnvNameIndex(owner.Username(), envName), Assert: txn.DocMissing, Insert: bson.M{}, } }
func (s *modelManagerSuite) modifyAccess(c *gc.C, user names.UserTag, action params.ModelAction, access params.ModelAccessPermission, model names.ModelTag) error { args := params.ModifyModelAccessRequest{ Changes: []params.ModifyModelAccess{{ UserTag: user.String(), Action: action, Access: access, ModelTag: model.String(), }}} result, err := s.modelmanager.ModifyModelAccess(args) c.Assert(err, jc.ErrorIsNil) return result.OneError() }
func (s *modelManagerSuite) createArgs(c *gc.C, owner names.UserTag) params.ModelCreateArgs { return params.ModelCreateArgs{ OwnerTag: owner.String(), Account: make(map[string]interface{}), Config: map[string]interface{}{ "name": "test-model", "authorized-keys": "ssh-key", // And to make it a valid dummy config "state-server": false, }, } }
// 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() id := envUserID(st.EnvironTag().Id(), user.Username()) err := envUsers.FindId(id).One(&envUser.doc) if err == mgo.ErrNotFound { return nil, errors.NotFoundf("envUser %q", user.Username()) } return envUser, nil }
func addTestingService(c *gc.C, st *State, series, name string, ch *Charm, owner names.UserTag, bindings map[string]string, storage map[string]StorageConstraints) *Service { c.Assert(ch, gc.NotNil) service, err := st.AddService(AddServiceArgs{ Name: name, Series: series, Owner: owner.String(), Charm: ch, EndpointBindings: bindings, Storage: storage, }) c.Assert(err, jc.ErrorIsNil) return service }
// 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 }
// 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, doc := createEnvUserOpAndDoc(envuuid, user, createdBy, displayName) err := st.runTransaction([]txn.Op{op}) if err == txn.ErrAborted { err = errors.AlreadyExistsf("environment user %q", user.Username()) } if err != nil { return nil, errors.Trace(err) } return &EnvironmentUser{st: st, doc: *doc}, nil }
// createEnvironmentOp returns the operation needed to create // an environment document with the given name and UUID. func createEnvironmentOp(st *State, owner names.UserTag, name, uuid, server string) txn.Op { doc := &environmentDoc{ UUID: uuid, Name: name, Life: Alive, Owner: owner.Username(), ServerUUID: server, } return txn.Op{ C: environmentsC, 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.Username()) err := envUsers.FindId(username).One(&envUser.doc) if err == mgo.ErrNotFound { return nil, errors.NotFoundf("environment user %q", user.Username()) } // 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 }
// 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, } }
// CreateLocalLoginMacaroon creates a local login macaroon for the // authenticated user. func (c *Client) CreateLocalLoginMacaroon(tag names.UserTag) (*macaroon.Macaroon, error) { args := params.Entities{Entities: []params.Entity{{tag.String()}}} var results params.MacaroonResults if err := c.facade.FacadeCall("CreateLocalLoginMacaroon", args, &results); err != nil { return nil, errors.Trace(err) } if n := len(results.Results); n != 1 { logger.Errorf("expected 1 result, got %#v", results) return nil, errors.Errorf("expected 1 result, got %d", n) } result := results.Results[0] if result.Error != nil { return nil, errors.Trace(result.Error) } return result.Result, 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(nil, fmt.Sprintf("model user %q does not exist", user.Canonical())) } if err != nil { return errors.Trace(err) } return nil }
func createInitialUserOp(st *State, user names.UserTag, password string) txn.Op { doc := userDoc{ Name: user.Name(), DisplayName: user.Name(), PasswordHash: password, // Empty PasswordSalt means utils.CompatSalt CreatedBy: user.Name(), DateCreated: nowToTheSecond(), } return txn.Op{ C: usersC, Id: doc.Name, Assert: txn.DocMissing, Insert: &doc, } }
// RemoveEnvironmentUser removes a user from the database. func (st *State) RemoveEnvironmentUser(user names.UserTag) error { ops := []txn.Op{{ C: envUsersC, Id: envUserID(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.Username())) } if err != nil { return errors.Trace(err) } return nil }
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 createEnvUserOp(envuuid string, user, createdBy names.UserTag, displayName string) txn.Op { creatorname := createdBy.Username() doc := &envUserDoc{ ID: envUserID(user), EnvUUID: envuuid, UserName: user.Username(), DisplayName: displayName, CreatedBy: creatorname, DateCreated: nowToTheSecond(), } return txn.Op{ C: envUsersC, Id: envUserID(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, } }
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, } }
// IsSystemAdministrator returns true if the user specified has access to the // state server environment (the system environment). func (st *State) IsSystemAdministrator(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.EnvironmentTag.Id() envUsers, userCloser := st.getRawCollection(envUsersC) defer userCloser() count, err := envUsers.Find(bson.D{ {"env-uuid", serverUUID}, {"user", user.Username()}, }).Count() if err != nil { return false, errors.Trace(err) } return count == 1, nil }
func createEnvUserOpAndDoc(envuuid string, user, createdBy names.UserTag, displayName string) (txn.Op, *envUserDoc) { username := user.Username() creatorname := createdBy.Username() id := envUserID(envuuid, username) doc := &envUserDoc{ ID: id, EnvUUID: envuuid, UserName: username, DisplayName: displayName, CreatedBy: creatorname, DateCreated: nowToTheSecond(), } op := txn.Op{ C: envUsersC, Id: id, Assert: txn.DocMissing, Insert: doc, } return op, 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 }
func (st *State) envSetupOps(cfg *config.Config, envUUID, serverUUID string, owner names.UserTag) ([]txn.Op, error) { if err := checkEnvironConfig(cfg); err != nil { return nil, errors.Trace(err) } // When creating the state server environment, the new environment // UUID is also used as the state server UUID. if serverUUID == "" { serverUUID = envUUID } envUserOp := createEnvUserOp(envUUID, owner, owner, owner.Name(), false) ops := []txn.Op{ createConstraintsOp(st, environGlobalKey, constraints.Value{}), createSettingsOp(environGlobalKey, cfg.AllAttrs()), incHostedEnvironCountOp(), createEnvironmentOp(st, owner, cfg.Name(), envUUID, serverUUID), createUniqueOwnerEnvNameOp(owner, cfg.Name()), envUserOp, } return ops, nil }
func (st *State) envSetupOps(cfg *config.Config, modelUUID, serverUUID string, owner names.UserTag) ([]txn.Op, error) { if err := checkModelConfig(cfg); err != nil { return nil, errors.Trace(err) } // When creating the state server model, the new model // UUID is also used as the state server UUID. if serverUUID == "" { serverUUID = modelUUID } modelUserOp := createModelUserOp(modelUUID, owner, owner, owner.Name(), false) ops := []txn.Op{ createConstraintsOp(st, modelGlobalKey, constraints.Value{}), createSettingsOp(modelGlobalKey, cfg.AllAttrs()), incHostedModelCountOp(), createModelOp(st, owner, cfg.Name(), modelUUID, serverUUID), createUniqueOwnerModelNameOp(owner, cfg.Name()), modelUserOp, } return ops, 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.Username()) 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.Username() == user.Username() { return nil } return common.ErrPerm }