// 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 }
// 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 }
// 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 }
// Authenticate authenticates the provided entity. If there is no macaroon provided, it will // return a *DischargeRequiredError containing a macaroon that can be used to grant access. func (m *ExternalMacaroonAuthenticator) Authenticate(entityFinder EntityFinder, _ names.Tag, req params.LoginRequest) (state.Entity, error) { declared, err := m.Service.CheckAny(req.Macaroons, nil, checkers.New(checkers.TimeBefore)) if _, ok := errors.Cause(err).(*bakery.VerificationError); ok { return nil, m.newDischargeRequiredError(err) } if err != nil { return nil, errors.Trace(err) } username := declared[usernameKey] var tag names.UserTag if names.IsValidUserName(username) { // The name is a local name without an explicit @local suffix. // In this case, for compatibility with 3rd parties that don't // care to add their own domain, we add an @external domain // to ensure there is no confusion between local and external // users. // TODO(rog) remove this logic when deployed dischargers // always add an @ domain. tag = names.NewLocalUserTag(username).WithDomain("external") } else { // We have a name with an explicit domain (or an invalid user name). if !names.IsValidUser(username) { return nil, errors.Errorf("%q is an invalid user name", username) } tag = names.NewUserTag(username) if tag.IsLocal() { return nil, errors.Errorf("external identity provider has provided ostensibly local name %q", username) } } 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 }
// NewEnvironment creates a new environment with its own UUID and // prepares it for use. Environment and State instances for the new // environment are returned. // // The state server environment's UUID is attached to the new // environment's document. Having the server UUIDs stored with each // environment document means that we have a way to represent external // environments, perhaps for future use around cross environment // relations. func (st *State) NewEnvironment(cfg *config.Config, owner names.UserTag) (_ *Environment, _ *State, err error) { if owner.IsLocal() { if _, err := st.User(owner); err != nil { return nil, nil, errors.Annotate(err, "cannot create environment") } } ssEnv, err := st.StateServerEnvironment() if err != nil { return nil, nil, errors.Annotate(err, "could not load state server environment") } uuid, ok := cfg.UUID() if !ok { return nil, nil, errors.Errorf("environment uuid was not supplied") } newState, err := st.ForEnviron(names.NewEnvironTag(uuid)) if err != nil { return nil, nil, errors.Annotate(err, "could not create state for new environment") } 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 environment") } err = newState.runTransactionNoEnvAliveAssert(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. environments, closer := st.getCollection(environmentsC) defer closer() envCount, countErr := environments.Find(bson.D{ {"owner", owner.Username()}, {"name", cfg.Name()}}, ).Count() if countErr != nil { err = errors.Trace(countErr) } else if envCount > 0 { err = errors.AlreadyExistsf("environment %q for %s", cfg.Name(), owner.Username()) } else { err = errors.New("environment already exists") } } if err != nil { return nil, nil, errors.Trace(err) } newEnv, err := newState.Environment() if err != nil { return nil, nil, errors.Trace(err) } return newEnv, newState, nil }
// Run implements Command.Run func (c *loginCommand) Run(ctx *cmd.Context) error { if c.loginAPIOpen == nil { c.loginAPIOpen = c.JujuCommandBase.APIOpen } // TODO(thumper): as we support the user and address // change this check here. if c.Server.Path == "" { return errors.New("no server file specified") } serverYAML, err := c.Server.Read(ctx) if err != nil { return errors.Trace(err) } var serverDetails envcmd.ServerFile if err := goyaml.Unmarshal(serverYAML, &serverDetails); err != nil { return errors.Trace(err) } info := api.Info{ Addrs: serverDetails.Addresses, CACert: serverDetails.CACert, } var userTag names.UserTag if serverDetails.Username != "" { // Construct the api.Info struct from the provided values // and attempt to connect to the remote server before we do anything else. if !names.IsValidUser(serverDetails.Username) { return errors.Errorf("%q is not a valid username", serverDetails.Username) } userTag = names.NewUserTag(serverDetails.Username) if !userTag.IsLocal() { // Remote users do not have their passwords stored in Juju // so we never attempt to change them. c.KeepPassword = true } info.Tag = userTag } if serverDetails.Password != "" { info.Password = serverDetails.Password } if serverDetails.Password == "" || serverDetails.Username == "" { info.UseMacaroons = true } if c == nil { panic("nil c") } if c.loginAPIOpen == nil { panic("no loginAPIOpen") } apiState, err := c.loginAPIOpen(&info, api.DefaultDialOpts()) if err != nil { return errors.Trace(err) } defer apiState.Close() // If we get to here, the credentials supplied were sufficient to connect // to the Juju Controller and login. Now we cache the details. controllerInfo, err := c.cacheConnectionInfo(serverDetails, apiState) if err != nil { return errors.Trace(err) } ctx.Infof("cached connection details as controller %q", c.Name) // If we get to here, we have been able to connect to the API server, and // also have been able to write the cached information. Now we can change // the user's password to a new randomly generated strong password, and // update the cached information knowing that the likelihood of failure is // minimal. if !c.KeepPassword { if err := c.updatePassword(ctx, apiState, userTag, controllerInfo); err != nil { return errors.Trace(err) } } return errors.Trace(envcmd.SetCurrentController(ctx, c.Name)) }