From 698d7efe7fd19eac95cd6567acb438b51a3cdde8 Mon Sep 17 00:00:00 2001 From: danaelhe <42972711+danaelhe@users.noreply.github.com> Date: Thu, 13 Apr 2023 13:41:25 -0400 Subject: [PATCH] Auth "init", "switch", "remove" Forces Lowercase (#1362) * Auth init, switch, remove forces lowercase * check if context exists before switching * Add tests, update command_config * add strings.ToLower(Context) to doit --- commands/auth.go | 31 +++++++++++++++++++++++++------ commands/auth_test.go | 39 +++++++++++++++++++++++++++++++++++++++ commands/doit.go | 1 + 3 files changed, 65 insertions(+), 6 deletions(-) diff --git a/commands/auth.go b/commands/auth.go index 39a969188..75025fd5c 100644 --- a/commands/auth.go +++ b/commands/auth.go @@ -107,7 +107,7 @@ To remove accounts from the configuration file, you can run ` + "`" + `doctl aut You will need an API token, which you can generate in the control panel at https://cloud.digitalocean.com/account/api/tokens. -You can provide a name to this initialization via the `+"`"+`--context`+"`"+` flag, and then it will be saved as an "authentication context". Authentication contexts are accessible via `+"`"+`doctl auth switch`+"`"+`, which re-initializes doctl, or by providing the `+"`"+`--context`+"`"+` flag when using any doctl command (to specify that auth context for just one command). This enables you to use multiple DigitalOcean accounts with doctl, or tokens that have different authentication scopes. +You can provide a (case insensitive) name to this initialization via the `+"`"+`--context`+"`"+` flag, and then it will be saved as an "authentication context". Authentication contexts are accessible via `+"`"+`doctl auth switch`+"`"+`, which re-initializes doctl, or by providing the `+"`"+`--context`+"`"+` flag when using any doctl command (to specify that auth context for just one command). This enables you to use multiple DigitalOcean accounts with doctl, or tokens that have different authentication scopes. If the `+"`"+`--context`+"`"+` flag is not specified, a default authentication context will be created during initialization. @@ -146,9 +146,9 @@ To create new contexts, see the help for `+"`"+`doctl auth init`+"`"+`.`, Writer func RunAuthInit(retrieveUserTokenFunc func() (string, error)) func(c *CmdConfig) error { return func(c *CmdConfig) error { token := c.getContextAccessToken() - context := Context + context := strings.ToLower(Context) if context == "" { - context = viper.GetString("context") + context = strings.ToLower(viper.GetString("context")) } if token == "" { @@ -188,7 +188,7 @@ func RunAuthInit(retrieveUserTokenFunc func() (string, error)) func(c *CmdConfig // RunAuthRemove remove available auth contexts from the user's doctl config. func RunAuthRemove(c *CmdConfig) error { - context := Context + context := strings.ToLower(Context) if context == "" { return fmt.Errorf("You must provide a context name") @@ -243,9 +243,28 @@ func displayAuthContexts(out io.Writer, currentContext string, contexts map[stri // RunAuthSwitch changes the default context and writes it to the // configuration. func RunAuthSwitch(c *CmdConfig) error { - context := Context + context := strings.ToLower(Context) if context == "" { - context = viper.GetString("context") + context = strings.ToLower(viper.GetString("context")) + } + + // check that context exists + contextsAvail := viper.GetStringMap("auth-contexts") + contextsAvail[doctl.ArgDefaultContext] = true + keys := make([]string, 0) + for ctx := range contextsAvail { + keys = append(keys, ctx) + } + + var contextExists bool + for _, ctx := range keys { + if ctx == context { + contextExists = true + } + } + + if !contextExists { + return errors.New("context does not exist") } // The two lines below aren't required for doctl specific functionality, diff --git a/commands/auth_test.go b/commands/auth_test.go index 9132f7ec8..83479ef10 100644 --- a/commands/auth_test.go +++ b/commands/auth_test.go @@ -124,6 +124,45 @@ func TestAuthInitWithProvidedToken(t *testing.T) { }) } +func TestAuthForcesLowercase(t *testing.T) { + cfw := cfgFileWriter + viper.Set(doctl.ArgAccessToken, "valid-token") + defer func() { + cfgFileWriter = cfw + viper.Set(doctl.ArgAccessToken, nil) + }() + + retrieveUserTokenFunc := func() (string, error) { + return "", errors.New("should not have called this") + } + + cfgFileWriter = func() (io.WriteCloser, error) { return &nopWriteCloser{Writer: ioutil.Discard}, nil } + + withTestClient(t, func(config *CmdConfig, tm *tcMocks) { + tm.oauth.EXPECT().TokenInfo(gomock.Any()).Return(&do.OAuthTokenInfo{}, nil) + + contexts := map[string]interface{}{doctl.ArgDefaultContext: true, "TestCapitalCase": true} + context := "TestCapitalCase" + viper.Set("auth-contexts", contexts) + viper.Set("context", context) + + err := RunAuthInit(retrieveUserTokenFunc)(config) + assert.NoError(t, err) + + contexts = map[string]interface{}{doctl.ArgDefaultContext: true, "TestCapitalCase": true} + viper.Set("auth-contexts", contexts) + viper.Set("context", "contextDoesntExist") + err = RunAuthSwitch(config) + // should error because context doesn't exist + assert.Error(t, err) + + viper.Set("context", "testcapitalcase") + err = RunAuthSwitch(config) + // should not error because context does exist + assert.NoError(t, err) + }) +} + func TestAuthList(t *testing.T) { buf := &bytes.Buffer{} config := &CmdConfig{Out: buf} diff --git a/commands/doit.go b/commands/doit.go index 42314f909..48c9b0e30 100644 --- a/commands/doit.go +++ b/commands/doit.go @@ -112,6 +112,7 @@ func initConfig() { viper.SetDefault("output", "text") viper.SetDefault(doctl.ArgContext, doctl.ArgDefaultContext) + Context = strings.ToLower(Context) if _, err := os.Stat(cfgFile); err == nil { if err := viper.ReadInConfig(); err != nil {