// AddUser adds a user to the database. func (st *State) AddUser(name, displayName, password, creator string) (*User, error) { if !names.IsValidUserName(name) { return nil, errors.Errorf("invalid user name %q", name) } salt, err := utils.RandomSalt() if err != nil { return nil, err } user := &User{ st: st, doc: userDoc{ Name: name, DisplayName: displayName, PasswordHash: utils.UserPasswordHash(password, salt), PasswordSalt: salt, CreatedBy: creator, DateCreated: nowToTheSecond(), }, } ops := []txn.Op{{ C: usersC, Id: name, Assert: txn.DocMissing, Insert: &user.doc, }} err = st.runTransaction(ops) if err == txn.ErrAborted { err = errors.New("user already exists") } if err != nil { return nil, errors.Trace(err) } return user, nil }
// RemoveUser removes a user. func (api *UserManagerAPI) RemoveUser(args params.Entities) (params.ErrorResults, error) { result := params.ErrorResults{ Results: make([]params.ErrorResult, len(args.Entities)), } if len(args.Entities) == 0 { return result, nil } for i, arg := range args.Entities { if !names.IsValidUserName(arg.Tag) { result.Results[i].Error = common.ServerError(errors.Errorf("%q is not a valid username", arg.Tag)) continue } user, err := api.state.User(names.NewLocalUserTag(arg.Tag)) if err != nil { result.Results[i].Error = common.ServerError(common.ErrPerm) continue } err = user.Deactivate() if err != nil { result.Results[i].Error = common.ServerError(fmt.Errorf("Failed to remove user: %s", err)) continue } } return result, nil }
func (s *userSuite) TestIsValidUserNameOrDomain(c *gc.C) { for i, t := range []struct { string string expect bool }{ {"", false}, {"bob", true}, {"Bob", true}, {"bOB", true}, {"b^b", false}, {"bob1", true}, {"bob-1", true}, {"bob+1", true}, {"bob+", false}, {"+bob", false}, {"bob.1", true}, {"1bob", true}, {"1-bob", true}, {"1+bob", true}, {"1.bob", true}, {"jim.bob+99-1.", false}, {"a", false}, {"0foo", true}, {"foo bar", false}, {"bar{}", false}, {"bar+foo", true}, {"bar_foo", false}, {"bar!", false}, {"bar^", false}, {"bar*", false}, {"foo=bar", false}, {"foo?", false}, {"[bar]", false}, {"'foo'", false}, {"%bar", false}, {"&bar", false}, {"#1foo", false}, {"[email protected]", false}, {"bar@local", false}, {"bar@ubuntuone", false}, {"bar@", false}, {"@local", false}, {"not/valid", false}, } { c.Logf("test %d: %s", i, t.string) c.Assert(names.IsValidUserName(t.string), gc.Equals, t.expect, gc.Commentf("%s", t.string)) c.Assert(names.IsValidUserDomain(t.string), gc.Equals, t.expect, gc.Commentf("%s", t.string)) } }
func (c *Client) userCall(username string, methodCall string) error { if !names.IsValidUserName(username) { return errors.Errorf("%q is not a valid username", username) } tag := names.NewLocalUserTag(username) var results params.ErrorResults args := params.Entities{ []params.Entity{{tag.String()}}, } err := c.facade.FacadeCall(methodCall, args, &results) if err != nil { return errors.Trace(err) } return results.OneError() }
// SetPassword changes the password for the specified user. func (c *Client) SetPassword(username, password string) error { if !names.IsValidUserName(username) { return errors.Errorf("%q is not a valid username", username) } tag := names.NewLocalUserTag(username) args := params.EntityPasswords{ Changes: []params.EntityPassword{{ Tag: tag.String(), Password: password}}, } var results params.ErrorResults err := c.facade.FacadeCall("SetPassword", args, &results) if err != nil { return err } return results.OneError() }
// UserInfo returns information about the specified users. If no users are // specified, the call should return all users. If includeDisabled is set to // ActiveUsers, only enabled users are returned. func (c *Client) UserInfo(usernames []string, all IncludeDisabled) ([]params.UserInfo, error) { var results params.UserInfoResults var entities []params.Entity for _, username := range usernames { if !names.IsValidUserName(username) { return nil, errors.Errorf("%q is not a valid username", username) } tag := names.NewLocalUserTag(username) entities = append(entities, params.Entity{Tag: tag.String()}) } args := params.UserInfoRequest{ Entities: entities, IncludeDisabled: bool(all), } err := c.facade.FacadeCall("UserInfo", args, &results) if err != nil { return nil, errors.Trace(err) } // Only need to look for errors if users were explicitly specified, because // if we didn't ask for any, we should get all, and we shouldn't get any // errors for listing all. We care here because we index into the users // slice. if len(results.Results) == len(usernames) { var errorStrings []string for i, result := range results.Results { if result.Error != nil { annotated := errors.Annotate(result.Error, usernames[i]) errorStrings = append(errorStrings, annotated.Error()) } } if len(errorStrings) > 0 { return nil, errors.New(strings.Join(errorStrings, ", ")) } } info := []params.UserInfo{} for i, result := range results.Results { if result.Result == nil { return nil, errors.Errorf("unexpected nil result at position %d", i) } info = append(info, *result.Result) } return info, nil }
func (api *UserManagerAPI) SetPassword(args ModifyUsers) (params.ErrorResults, error) { result := params.ErrorResults{ Results: make([]params.ErrorResult, len(args.Changes)), } if len(args.Changes) == 0 { return result, nil } for i, arg := range args.Changes { loggedInUser := api.getLoggedInUser() if _, ok := loggedInUser.(names.UserTag); !ok { result.Results[i].Error = common.ServerError(fmt.Errorf("Not a user")) continue } username := arg.Username if username == "" { username = arg.Tag } if !names.IsValidUserName(username) { result.Results[i].Error = common.ServerError(errors.Errorf("%q is not a valid username", arg.Tag)) continue } argUser, err := api.state.User(names.NewLocalUserTag(username)) if err != nil { result.Results[i].Error = common.ServerError(fmt.Errorf("Failed to find user %v", err)) continue } if loggedInUser != argUser.Tag() { result.Results[i].Error = common.ServerError(fmt.Errorf("Can only change the password of the current user (%s)", loggedInUser.Id())) continue } err = argUser.SetPassword(arg.Password) if err != nil { result.Results[i].Error = common.ServerError(fmt.Errorf("Failed to set password %v", err)) continue } } return result, nil }
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 }
// 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 }
// Run implements Command.Run. func (c *changePasswordCommand) Run(ctx *cmd.Context) error { if c.api == nil { api, err := c.NewUserManagerAPIClient() if err != nil { return errors.Trace(err) } c.api = api defer c.api.Close() } newPassword, err := generateOrReadPassword(ctx, c.Generate) if err != nil { return errors.Trace(err) } var accountName string controllerName := c.ControllerName() store := c.ClientStore() if c.User != "" { if !names.IsValidUserName(c.User) { return errors.NotValidf("user name %q", c.User) } accountName = names.NewUserTag(c.User).Canonical() } else { accountName, err = store.CurrentAccount(controllerName) if err != nil { return errors.Trace(err) } } accountDetails, err := store.AccountByName(controllerName, accountName) if err != nil && !errors.IsNotFound(err) { return errors.Trace(err) } if accountDetails != nil && accountDetails.Macaroon == "" { // Generate a macaroon first to guard against I/O failures // occurring after the password has been changed, preventing // future logins. userTag := names.NewUserTag(accountName) macaroon, err := c.api.CreateLocalLoginMacaroon(userTag) if err != nil { return errors.Trace(err) } accountDetails.Password = "" // TODO(axw) update jujuclient with code for marshalling // and unmarshalling macaroons as YAML. macaroonJSON, err := macaroon.MarshalJSON() if err != nil { return errors.Trace(err) } accountDetails.Macaroon = string(macaroonJSON) if err := store.UpdateAccount(controllerName, accountName, *accountDetails); err != nil { return errors.Annotate(err, "failed to update client credentials") } } if err := c.api.SetPassword(accountName, newPassword); err != nil { return block.ProcessBlockedError(err, block.BlockChange) } if accountDetails == nil { ctx.Infof("Password for %q has been updated.", c.User) } else { ctx.Infof("Your password has been updated.") } return nil }
// Run implements Command.Run. func (c *loginCommand) Run(ctx *cmd.Context) error { controllerName := c.ControllerName() store := c.ClientStore() user := c.User if user == "" { // The username has not been specified, so prompt for it. fmt.Fprint(ctx.Stderr, "username: "******"" { return errors.Errorf("you must specify a username") } } if !names.IsValidUserName(user) { return errors.NotValidf("user name %q", user) } userTag := names.NewUserTag(user) accountName := userTag.Canonical() // Make sure that the client is not already logged in, // or if it is, that it is logged in as the specified // user. currentAccountName, err := store.CurrentAccount(controllerName) if err == nil { if currentAccountName != accountName { return errors.New(`already logged in Run "juju logout" first before attempting to log in as a different user. `) } } else if !errors.IsNotFound(err) { return errors.Trace(err) } accountDetails, err := store.AccountByName(controllerName, accountName) if err != nil && !errors.IsNotFound(err) { return errors.Trace(err) } // Read password from the terminal, and attempt to log in using that. password, err := readAndConfirmPassword(ctx) if err != nil { return errors.Trace(err) } params, err := c.NewAPIConnectionParams(store, controllerName, "", "") if err != nil { return errors.Trace(err) } if accountDetails != nil { accountDetails.Password = password } else { accountDetails = &jujuclient.AccountDetails{ User: accountName, Password: password, } } params.AccountDetails = accountDetails api, err := c.newLoginAPI(params) if err != nil { return errors.Annotate(err, "creating API connection") } defer api.Close() // Create a new local login macaroon, and update the account details // in the client store, removing the recorded password (if any) and // storing the macaroon. macaroon, err := api.CreateLocalLoginMacaroon(userTag) if err != nil { return errors.Annotate(err, "failed to create a temporary credential") } macaroonJSON, err := macaroon.MarshalJSON() if err != nil { return errors.Annotate(err, "marshalling temporary credential to JSON") } accountDetails.Password = "" accountDetails.Macaroon = string(macaroonJSON) if err := store.UpdateAccount(controllerName, accountName, *accountDetails); err != nil { return errors.Annotate(err, "failed to record temporary credential") } if err := store.SetCurrentAccount(controllerName, accountName); err != nil { return errors.Annotate(err, "failed to set current account") } ctx.Infof("You are now logged in to %q as %q.", controllerName, accountName) return nil }