diff --git a/internal/auth/auth.go b/internal/auth/auth.go index 44996c1a8..c90a271e5 100644 --- a/internal/auth/auth.go +++ b/internal/auth/auth.go @@ -322,6 +322,10 @@ func (v verifier) performAuthCheck() (aclResults perms.ACLResults, userId string } if at != nil { userId = at.GetIamUserId() + if userId == "" { + v.logger.Warn("perform auth check: valid token did not map to a user, likely because no account is associated with the user any longer; continuing as u_anon", "token_id", at.GetPublicId()) + userId = "u_anon" + } } } diff --git a/internal/cmd/commands.go b/internal/cmd/commands.go index fc6b813b8..ca2287208 100644 --- a/internal/cmd/commands.go +++ b/internal/cmd/commands.go @@ -717,6 +717,24 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { Func: "list", }, nil }, + "users add-accounts": func() (cli.Command, error) { + return &users.Command{ + Command: base.NewCommand(ui), + Func: "add-accounts", + }, nil + }, + "users set-accounts": func() (cli.Command, error) { + return &users.Command{ + Command: base.NewCommand(ui), + Func: "set-accounts", + }, nil + }, + "users remove-accounts": func() (cli.Command, error) { + return &users.Command{ + Command: base.NewCommand(ui), + Func: "remove-accounts", + }, nil + }, } } diff --git a/internal/cmd/commands/groups/funcs.go b/internal/cmd/commands/groups/funcs.go index 2c91a3159..e67c4fd19 100644 --- a/internal/cmd/commands/groups/funcs.go +++ b/internal/cmd/commands/groups/funcs.go @@ -51,7 +51,7 @@ func removeMembersHelp() string { "", ` Removes members (users) from a group given its ID. The "user" flag can be specified multiple times. Example:`, "", - ` $ boundary groups remove-members -id r_1234567890 -user u_1234567890`, + ` $ boundary groups remove-members -id g_1234567890 -user u_1234567890`, }) } @@ -121,5 +121,6 @@ func generateGroupTableOutput(in *groups.Group) string { ) } } + return base.WrapForHelpText(ret) } diff --git a/internal/cmd/commands/groups/group.go b/internal/cmd/commands/groups/group.go index 3a4e9f173..20d9b0521 100644 --- a/internal/cmd/commands/groups/group.go +++ b/internal/cmd/commands/groups/group.go @@ -116,6 +116,7 @@ func (c *Command) Run(args []string) int { default: opts = append(opts, groups.WithName(c.FlagName)) } + switch c.FlagDescription { case "": case "null": @@ -142,7 +143,6 @@ func (c *Command) Run(args []string) int { members = nil } } - } groupClient := groups.NewClient(client) diff --git a/internal/cmd/commands/users/funcs.go b/internal/cmd/commands/users/funcs.go index b2b4dc010..01c1b338d 100644 --- a/internal/cmd/commands/users/funcs.go +++ b/internal/cmd/commands/users/funcs.go @@ -1,12 +1,75 @@ package users import ( + "fmt" + "strings" "time" "github.com/hashicorp/boundary/api/users" "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/internal/cmd/common" + "github.com/hashicorp/boundary/internal/types/resource" + "github.com/mitchellh/go-wordwrap" ) +func accountSynopsisFunc(inFunc string) string { + var in string + switch { + case strings.HasPrefix(inFunc, "add"): + in = "Add accounts to" + case strings.HasPrefix(inFunc, "set"): + in = "Set the full contents of the accounts on" + case strings.HasPrefix(inFunc, "remove"): + in = "Remove accounts from" + } + return wordwrap.WrapString(fmt.Sprintf("%s a user within Boundary", in), base.TermWidth) +} + +func addAccountsHelp() string { + return base.WrapForHelpText([]string{ + "Usage: boundary users add-accounts [options] [args]", + "", + ` Associates accounts to a user given its ID. The "account" flag can be specified multiple times. Example:`, + "", + ` $ boundary users add-accounts -id u_1234567890 -account a_1234567890`, + }) +} + +func setAccountsHelp() string { + return base.WrapForHelpText([]string{ + "Usage: boundary users set-accounts [options] [args]", + "", + ` Sets the complete set of associated accounts on a user given its ID. The "account" flag can be specified multiple times. Example:`, + "", + ` $ boundary users set-principals -id u_1234567890 -account a_0987654321 -account a_1234567890`, + }) +} + +func removeAccountsHelp() string { + return base.WrapForHelpText([]string{ + "Usage: boundary users remove-accounts [options] [args]", + "", + ` Disassociates accounts from a user given its ID. The "account" flag can be specified multiple times. Example:`, + "", + ` $ boundary users remove-accounts -id u_1234567890 -account a_1234567890`, + }) +} + +func populateFlags(c *Command, f *base.FlagSet, flagNames []string) { + common.PopulateCommonFlags(c.Command, f, resource.User.String(), flagNames) + + for _, name := range flagNames { + switch name { + case "account": + f.StringSliceVar(&base.StringSliceVar{ + Name: "account", + Target: &c.flagAccounts, + Usage: "The accounts to add, remove, or set. May be specified multiple times.", + }) + } + } +} + func generateUserTableOutput(in *users.User) string { nonAttributeMap := map[string]interface{}{ "ID": in.Id, @@ -24,6 +87,20 @@ func generateUserTableOutput(in *users.User) string { maxLength := base.MaxAttributesLength(nonAttributeMap, nil, nil) + var userMaps []map[string]interface{} + if len(in.Accounts) > 0 { + for _, account := range in.Accounts { + a := map[string]interface{}{ + "ID": account.Id, + "Scope ID": account.ScopeId, + } + userMaps = append(userMaps, a) + } + if l := len("Scope ID"); l > maxLength { + maxLength = l + } + } + ret := []string{ "", "User information:", @@ -33,5 +110,18 @@ func generateUserTableOutput(in *users.User) string { base.ScopeInfoForOutput(in.Scope, maxLength), } + if len(in.Accounts) > 0 { + ret = append(ret, + "", + " Accounts:", + ) + for _, m := range userMaps { + ret = append(ret, + base.WrapMap(4, maxLength, m), + "", + ) + } + } + return base.WrapForHelpText(ret) } diff --git a/internal/cmd/commands/users/user.go b/internal/cmd/commands/users/user.go index 0a9beefc6..79d7d0dea 100644 --- a/internal/cmd/commands/users/user.go +++ b/internal/cmd/commands/users/user.go @@ -8,7 +8,6 @@ import ( "github.com/hashicorp/boundary/api/users" "github.com/hashicorp/boundary/internal/cmd/base" "github.com/hashicorp/boundary/internal/cmd/common" - "github.com/hashicorp/boundary/internal/types/resource" "github.com/hashicorp/boundary/sdk/strutil" "github.com/mitchellh/cli" "github.com/posener/complete" @@ -21,26 +20,45 @@ type Command struct { *base.Command Func string + + flagAccounts []string } func (c *Command) Synopsis() string { - return common.SynopsisFunc(c.Func, "user") + switch c.Func { + case "", "create", "update", "read", "delete", "list": + return common.SynopsisFunc(c.Func, "user") + case "add-accounts", "set-accounts", "remove-accounts": + return accountSynopsisFunc(c.Func) + } + return "" +} + +var helpMap = func() map[string]func() string { + ret := common.HelpMap("user") + ret["add-accounts"] = addAccountsHelp + ret["set-accounts"] = setAccountsHelp + ret["remove-accounts"] = removeAccountsHelp + return ret } var flagsMap = map[string][]string{ - "create": {"scope-id", "name", "description"}, - "update": {"id", "name", "description", "version"}, - "read": {"id"}, - "delete": {"id"}, - "list": {"scope-id"}, + "create": {"scope-id", "name", "description"}, + "update": {"id", "name", "description", "version"}, + "read": {"id"}, + "delete": {"id"}, + "list": {"scope-id"}, + "add-accounts": {"id", "account", "version"}, + "set-accounts": {"id", "account", "version"}, + "remove-accounts": {"id", "account", "version"}, } func (c *Command) Help() string { - helpMap := common.HelpMap("user") + hm := helpMap() if c.Func == "" { - return helpMap["base"]() + return hm["base"]() } - return helpMap[c.Func]() + c.Flags().Help() + return hm[c.Func]() + c.Flags().Help() } func (c *Command) Flags() *base.FlagSets { @@ -48,7 +66,7 @@ func (c *Command) Flags() *base.FlagSets { if len(flagsMap[c.Func]) > 0 { f := set.NewFlagSet("Command Options") - common.PopulateCommonFlags(c.Command, f, resource.User.String(), flagsMap[c.Func]) + populateFlags(c, f, flagsMap[c.Func]) } return set @@ -107,6 +125,26 @@ func (c *Command) Run(args []string) int { opts = append(opts, users.WithDescription(c.FlagDescription)) } + accounts := c.flagAccounts + switch c.Func { + case "add-accounts", "remove-accounts": + if len(c.flagAccounts) == 0 { + c.UI.Error("No accounts supplied via -account") + return 1 + } + + case "set-accounts": + switch len(c.flagAccounts) { + case 0: + c.UI.Error("No accounts supplied via -account") + return 1 + case 1: + if c.flagAccounts[0] == "null" { + accounts = nil + } + } + } + userClient := users.NewClient(client) // Perform check-and-set when needed @@ -142,6 +180,12 @@ func (c *Command) Run(args []string) int { } case "list": listResult, err = userClient.List(c.Context, c.FlagScopeId, opts...) + case "add-accounts": + result, err = userClient.AddAccounts(c.Context, c.FlagId, version, accounts, opts...) + case "set-accounts": + result, err = userClient.SetAccounts(c.Context, c.FlagId, version, accounts, opts...) + case "remove-accounts": + result, err = userClient.RemoveAccounts(c.Context, c.FlagId, version, accounts, opts...) } plural := "user" diff --git a/internal/cmd/common/help.go b/internal/cmd/common/help.go index 9ba1ee5ca..0e06821c0 100644 --- a/internal/cmd/common/help.go +++ b/internal/cmd/common/help.go @@ -29,13 +29,14 @@ func HelpMap(resType string) map[string]func() string { resource.Scope.String(): "o", resource.AuthToken.String(): "at", resource.AuthMethod.String(): "am", + resource.Account.String(): "a", resource.Role.String(): "r", resource.Group.String(): "g", resource.User.String(): "u", resource.HostCatalog.String(): "hc", resource.HostSet.String(): "hs", resource.Host.String(): "h", - resource.Session.String(): "ss", + resource.Session.String(): "s", resource.Target.String(): "t", } return map[string]func() string{