Repository: digitalocean/doctl
Pull request #1385 (2023-06-28 15:21:47)

account: protect against panic in user context.
A bit of an edge case that most users will never run into, but calling `doctl account get` using a user context token panics:

```
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0xa0ad1a]

goroutine 1 [running]:
github.com/digitalocean/doctl/commands/displayers.(*Account).KV(0xc0002d00b8)
	/home/runner/work/doctl/doctl/commands/displayers/account.go:49 +0x23a
github.com/digitalocean/doctl/commands/displayers.DisplayText({0x12b0c38, 0xc0002d00b8}, {0x12a7040, 0xc0001a0008}, 0x0, {0x0, 0x0, 0x2?})
	/home/runner/work/doctl/doctl/commands/displayers/output.go:91 +0x34b
github.com/digitalocean/doctl/commands/displayers.(*Displayer).Display(0xc00078fc50)
	/home/runner/work/doctl/doctl/commands/displayers/output.go:61 +0x225
github.com/digitalocean/doctl/commands.(*CmdConfig).Display(0xc0003f2000, {0x12b0c38?, 0xc0002d00b8?})
	/home/runner/work/doctl/doctl/commands/command_config.go:224 +0x135
github.com/digitalocean/doctl/commands.RunAccountGet(0xc0003f2000)
	/home/runner/work/doctl/doctl/commands/account.go:61 +0x7f
github.com/digitalocean/doctl/commands.cmdBuilderWithInit.func1(0xc0004f6a00?, {0xc0001acca0, 0x0, 0x2})
	/home/runner/work/doctl/doctl/commands/command.go:83 +0xe4
github.com/spf13/cobra.(*Command).execute(0xc0004f6a00, {0xc0001acc80, 0x2, 0x2})
	/home/runner/work/doctl/doctl/vendor/github.com/spf13/cobra/command.go:860 +0x663
github.com/spf13/cobra.(*Command).ExecuteC(0x19d8b80)
	/home/runner/work/doctl/doctl/vendor/github.com/spf13/cobra/command.go:974 +0x3bd
github.com/spf13/cobra.(*Command).Execute(...)
	/home/runner/work/doctl/doctl/vendor/github.com/spf13/cobra/command.go:902
github.com/digitalocean/doctl/commands.Execute()
	/home/runner/work/doctl/doctl/commands/doit.go:142 +0x27
main.main()
	/home/runner/work/doctl/doctl/cmd/doctl/main.go:24 +0x31
```


Patch:
diff --git a/commands/displayers/account.go b/commands/displayers/account.go
index 24362995..f274fcf0 100644
--- a/commands/displayers/account.go
+++ b/commands/displayers/account.go
@@ -46,7 +46,11 @@ func (a *Account) KV() []map[string]interface{} {
 	x := map[string]interface{}{
 		"Email": a.Email, "DropletLimit": a.DropletLimit,
 		"EmailVerified": a.EmailVerified, "UUID": a.UUID,
-		"Status": a.Status, "Team": a.Team.Name, "TeamUUID": a.Team.UUID,
+		"Status": a.Status,
+	}
+	if a.Team != nil {
+		x["Team"] = a.Team.Name
+		x["TeamUUID"] = a.Team.UUID
 	}
 
 	return []map[string]interface{}{x}


Test patch:
diff --git a/integration/account_test.go b/integration/account_test.go
index c738b99c..53997bc2 100644
--- a/integration/account_test.go
+++ b/integration/account_test.go
@@ -29,6 +29,10 @@ var _ = suite("account/get", func(t *testing.T, when spec.G, it spec.S) {
 			switch req.URL.Path {
 			case "/v2/account":
 				auth := req.Header.Get("Authorization")
+				if auth == "Bearer user-context-token" {
+					w.Write([]byte(userContextGetResponse))
+					return
+				}
 				if auth != "Bearer some-magic-token" {
 					w.WriteHeader(http.StatusUnauthorized)
 					return
@@ -81,6 +85,20 @@ var _ = suite("account/get", func(t *testing.T, when spec.G, it spec.S) {
 			expect.Equal(strings.TrimSpace(formattedAccountOutput), strings.TrimSpace(string(output)))
 		})
 	})
+
+	it("doesn't panic in a user context", func() {
+		cmd := exec.Command(builtBinaryPath,
+			"-t", "user-context-token",
+			"-u", server.URL,
+			"account",
+			"get",
+		)
+
+		output, err := cmd.CombinedOutput()
+		expect.NoError(err)
+
+		expect.Equal(strings.TrimSpace(userContextOutput), strings.TrimSpace(string(output)))
+	})
 })
 
 var _ = suite("account/ratelimit", func(t *testing.T, when spec.G, it spec.S) {
@@ -183,9 +201,28 @@ const (
     }
   }
 }`
+
+	userContextGetResponse = `
+{
+  "account": {
+    "droplet_limit": 25,
+    "floating_ip_limit": 5,
+    "email": "sammy@digitalocean.com",
+    "uuid": "b6fr89dbf6d9156cace5f3c78dc9851d957381ef",
+    "email_verified": true,
+    "status": "active",
+    "status_message": ""
+  }
+}`
+
 	accountOutput = `
 User Email                Team       Droplet Limit    Email Verified    User UUID                                   Status
 sammy@digitalocean.com    My Team    25               true              b6fr89dbf6d9156cace5f3c78dc9851d957381ef    active
+`
+
+	userContextOutput = `
+User Email                Team     Droplet Limit    Email Verified    User UUID                                   Status
+sammy@digitalocean.com    <nil>    25               true              b6fr89dbf6d9156cace5f3c78dc9851d957381ef    active
 `
 
 	formattedAccountOutput = `