From 618665bf8d2188cca09f59c30fd76001de600eec Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Tue, 5 Sep 2017 00:05:01 -0400 Subject: [PATCH] Update token-lookup command --- command/token_lookup.go | 162 ++++++++++++--------- command/token_lookup_test.go | 265 ++++++++++++++++++++++------------- 2 files changed, 261 insertions(+), 166 deletions(-) diff --git a/command/token_lookup.go b/command/token_lookup.go index c1c62ef716..5d51e666eb 100644 --- a/command/token_lookup.go +++ b/command/token_lookup.go @@ -5,96 +5,126 @@ import ( "strings" "github.com/hashicorp/vault/api" - "github.com/hashicorp/vault/meta" + "github.com/mitchellh/cli" + "github.com/posener/complete" ) -// TokenLookupCommand is a Command that outputs details about the -// provided. +// Ensure we are implementing the right interfaces. +var _ cli.Command = (*TokenLookupCommand)(nil) +var _ cli.CommandAutocomplete = (*TokenLookupCommand)(nil) + +// TokenLookupCommand is a Command that outputs details about the provided. type TokenLookupCommand struct { - meta.Meta + *BaseCommand + + flagAccessor bool +} + +func (c *TokenLookupCommand) Synopsis() string { + return "Displays information about a token" +} + +func (c *TokenLookupCommand) Help() string { + helpText := ` +Usage: vault token-lookup [options] [TOKEN | ACCESSOR] + + Displays information about a token or accessor. If a TOKEN is not provided, + the locally authenticated token is used. + + Get information about the locally authenticated token (this uses the + /auth/token/lookup-self endpoint and permission): + + $ vault token-lookup + + Get information about a particular token (this uses the /auth/token/lookup + endpoint and permission): + + $ vault token-lookup 96ddf4bc-d217-f3ba-f9bd-017055595017 + + Get information about a token via its accessor: + + $ vault token-lookup -accessor 9793c9b3-e04a-46f3-e7b8-748d7da248da + + For a full list of examples, please see the documentation. + +` + c.Flags().Help() + + return strings.TrimSpace(helpText) +} + +func (c *TokenLookupCommand) Flags() *FlagSets { + set := c.flagSet(FlagSetHTTP | FlagSetOutputFormat) + + f := set.NewFlagSet("Command Options") + + f.BoolVar(&BoolVar{ + Name: "accessor", + Target: &c.flagAccessor, + Default: false, + EnvVar: "", + Completion: complete.PredictNothing, + Usage: "Treat the argument as an accessor intead of a token. When " + + "this option is selected, the output will NOT include the token.", + }) + + return set +} + +func (c *TokenLookupCommand) AutocompleteArgs() complete.Predictor { + return c.PredictVaultFiles() +} + +func (c *TokenLookupCommand) AutocompleteFlags() complete.Flags { + return c.Flags().Completions() } func (c *TokenLookupCommand) Run(args []string) int { - var format string - var accessor bool - flags := c.Meta.FlagSet("token-lookup", meta.FlagSetDefault) - flags.BoolVar(&accessor, "accessor", false, "") - flags.StringVar(&format, "format", "table", "") - flags.Usage = func() { c.Ui.Error(c.Help()) } - if err := flags.Parse(args); err != nil { + f := c.Flags() + + if err := f.Parse(args); err != nil { + c.UI.Error(err.Error()) return 1 } - args = flags.Args() - if len(args) > 1 { - flags.Usage() - c.Ui.Error(fmt.Sprintf( - "\ntoken-lookup expects at most one argument")) + token := "" + + args = f.Args() + switch { + case c.flagAccessor && len(args) < 1: + c.UI.Error(fmt.Sprintf("Not enough arguments with -accessor (expected 1, got %d)", len(args))) + return 1 + case c.flagAccessor && len(args) > 1: + c.UI.Error(fmt.Sprintf("Too many arguments with -accessor (expected 1, got %d)", len(args))) + return 1 + case len(args) == 0: + // Use the local token + case len(args) == 1: + token = strings.TrimSpace(args[0]) + case len(args) > 1: + c.UI.Error(fmt.Sprintf("Too many arguments (expected 0-1, got %d)", len(args))) return 1 } client, err := c.Client() if err != nil { - c.Ui.Error(fmt.Sprintf( - "error initializing client: %s", err)) + c.UI.Error(err.Error()) return 2 } var secret *api.Secret switch { - case !accessor && len(args) == 0: + case token == "": secret, err = client.Auth().Token().LookupSelf() - case !accessor && len(args) == 1: - secret, err = client.Auth().Token().Lookup(args[0]) - case accessor && len(args) == 1: - secret, err = client.Auth().Token().LookupAccessor(args[0]) + case c.flagAccessor: + secret, err = client.Auth().Token().LookupAccessor(token) default: - // This happens only when accessor is set and no argument is passed - c.Ui.Error(fmt.Sprintf("token-lookup expects an argument when accessor flag is set")) - return 1 + secret, err = client.Auth().Token().Lookup(token) } if err != nil { - c.Ui.Error(fmt.Sprintf( - "error looking up token: %s", err)) - return 1 - } - return OutputSecret(c.Ui, format, secret) -} - -func doTokenLookup(args []string, client *api.Client) (*api.Secret, error) { - if len(args) == 0 { - return client.Auth().Token().LookupSelf() + c.UI.Error(fmt.Sprintf("Error looking up token: %s", err)) + return 2 } - token := args[0] - return client.Auth().Token().Lookup(token) -} - -func (c *TokenLookupCommand) Synopsis() string { - return "Display information about the specified token" -} - -func (c *TokenLookupCommand) Help() string { - helpText := ` -Usage: vault token-lookup [options] [token|accessor] - - Displays information about the specified token. If no token is specified, the - operation is performed on the currently authenticated token i.e. lookup-self. - Information about the token can be retrieved using the token accessor via the - '-accessor' flag. - -General Options: -` + meta.GeneralOptionsUsage() + ` -Token Lookup Options: - -accessor A boolean flag, if set, treats the argument as an accessor of the token. - Note that the response of the command when this is set, will not contain - the token ID. Accessor is only meant for looking up the token properties - (and for revocation via '/auth/token/revoke-accessor/' endpoint). - - -format=table The format for output. By default it is a whitespace- - delimited table. This can also be json or yaml. - -` - return strings.TrimSpace(helpText) + return OutputSecret(c.UI, c.flagFormat, secret) } diff --git a/command/token_lookup_test.go b/command/token_lookup_test.go index 143b9447d6..eeeaefd2b3 100644 --- a/command/token_lookup_test.go +++ b/command/token_lookup_test.go @@ -1,124 +1,189 @@ package command import ( + "strings" "testing" - "github.com/hashicorp/vault/api" - "github.com/hashicorp/vault/http" - "github.com/hashicorp/vault/meta" - "github.com/hashicorp/vault/vault" "github.com/mitchellh/cli" ) -func TestTokenLookupAccessor(t *testing.T) { - core, _, token := vault.TestCoreUnsealed(t) - ln, addr := http.TestServer(t, core) - defer ln.Close() +func testTokenLookupCommand(tb testing.TB) (*cli.MockUi, *TokenLookupCommand) { + tb.Helper() - ui := new(cli.MockUi) - c := &TokenLookupCommand{ - Meta: meta.Meta{ - ClientToken: token, - Ui: ui, + ui := cli.NewMockUi() + return ui, &TokenLookupCommand{ + BaseCommand: &BaseCommand{ + UI: ui, }, } - args := []string{ - "-address", addr, - } - c.Run(args) +} - // Create a new token for us to use - client, err := c.Client() - if err != nil { - t.Fatalf("err: %s", err) +func TestTokenLookupCommand_Run(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + args []string + out string + code int + }{ + { + "accessor_no_args", + []string{"-accessor"}, + "Not enough arguments", + 1, + }, + { + "accessor_too_many_args", + []string{"-accessor", "abcd1234", "efgh5678"}, + "Too many arguments", + 1, + }, + { + "too_many_args", + []string{"abcd1234", "efgh5678"}, + "Too many arguments", + 1, + }, + { + "format", + []string{"-format", "json"}, + "{", + 0, + }, + { + "format_bad", + []string{"-format", "nope-not-real"}, + "Invalid output format", + 1, + }, } - resp, err := client.Auth().Token().Create(&api.TokenCreateRequest{ - Lease: "1h", + + t.Run("validations", func(t *testing.T) { + t.Parallel() + + for _, tc := range cases { + tc := tc + + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + ui, cmd := testTokenLookupCommand(t) + cmd.client = client + + code := cmd.Run(tc.args) + if code != tc.code { + t.Errorf("expected %d to be %d", code, tc.code) + } + + combined := ui.OutputWriter.String() + ui.ErrorWriter.String() + if !strings.Contains(combined, tc.out) { + t.Errorf("expected %q to contain %q", combined, tc.out) + } + }) + } }) - if err != nil { - t.Fatalf("err: %s", err) - } - // Enable the accessor flag - args = append(args, "-accessor") + t.Run("token", func(t *testing.T) { + t.Parallel() - // Expect failure if no argument is passed when accessor flag is set - code := c.Run(args) - if code == 0 { - t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) - } + client, closer := testVaultServer(t) + defer closer() - // Add token accessor as arg - args = append(args, resp.Auth.Accessor) - code = c.Run(args) - if code != 0 { - t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) - } -} + token, _ := testTokenAndAccessor(t, client) -func TestTokenLookupSelf(t *testing.T) { - core, _, token := vault.TestCoreUnsealed(t) - ln, addr := http.TestServer(t, core) - defer ln.Close() + ui, cmd := testTokenLookupCommand(t) + cmd.client = client - ui := new(cli.MockUi) - c := &TokenLookupCommand{ - Meta: meta.Meta{ - ClientToken: token, - Ui: ui, - }, - } + code := cmd.Run([]string{ + token, + }) + if exp := 0; code != exp { + t.Errorf("expected %d to be %d", code, exp) + } - args := []string{ - "-address", addr, - } - - // Run it against itself - code := c.Run(args) - - // Verify it worked - if code != 0 { - t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) - } -} - -func TestTokenLookup(t *testing.T) { - core, _, token := vault.TestCoreUnsealed(t) - ln, addr := http.TestServer(t, core) - defer ln.Close() - - ui := new(cli.MockUi) - c := &TokenLookupCommand{ - Meta: meta.Meta{ - ClientToken: token, - Ui: ui, - }, - } - - args := []string{ - "-address", addr, - } - // Run it once for client - c.Run(args) - - // Create a new token for us to use - client, err := c.Client() - if err != nil { - t.Fatalf("err: %s", err) - } - resp, err := client.Auth().Token().Create(&api.TokenCreateRequest{ - Lease: "1h", + expected := token + combined := ui.OutputWriter.String() + ui.ErrorWriter.String() + if !strings.Contains(combined, expected) { + t.Errorf("expected %q to contain %q", combined, expected) + } }) - if err != nil { - t.Fatalf("err: %s", err) - } - // Add token as arg for real test and run it - args = append(args, resp.Auth.ClientToken) - code := c.Run(args) + t.Run("self", func(t *testing.T) { + t.Parallel() - // Verify it worked - if code != 0 { - t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) - } + client, closer := testVaultServer(t) + defer closer() + + ui, cmd := testTokenLookupCommand(t) + cmd.client = client + + code := cmd.Run([]string{}) + if exp := 0; code != exp { + t.Errorf("expected %d to be %d", code, exp) + } + + expected := "display_name" + combined := ui.OutputWriter.String() + ui.ErrorWriter.String() + if !strings.Contains(combined, expected) { + t.Errorf("expected %q to contain %q", combined, expected) + } + }) + + t.Run("accessor", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + _, accessor := testTokenAndAccessor(t, client) + + ui, cmd := testTokenLookupCommand(t) + cmd.client = client + + code := cmd.Run([]string{ + "-accessor", + accessor, + }) + if exp := 0; code != exp { + t.Errorf("expected %d to be %d", code, exp) + } + + expected := accessor + combined := ui.OutputWriter.String() + ui.ErrorWriter.String() + if !strings.Contains(combined, expected) { + t.Errorf("expected %q to contain %q", combined, expected) + } + }) + + t.Run("communication_failure", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServerBad(t) + defer closer() + + ui, cmd := testTokenLookupCommand(t) + cmd.client = client + + code := cmd.Run([]string{}) + if exp := 2; code != exp { + t.Errorf("expected %d to be %d", code, exp) + } + + expected := "Error looking up token: " + combined := ui.OutputWriter.String() + ui.ErrorWriter.String() + if !strings.Contains(combined, expected) { + t.Errorf("expected %q to contain %q", combined, expected) + } + }) + + t.Run("no_tabs", func(t *testing.T) { + t.Parallel() + + _, cmd := testTokenLookupCommand(t) + assertNoTabs(t, cmd) + }) }