Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: validate access token #308

Merged
merged 5 commits into from
May 29, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions cmd/cmdtest.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"github.com/stretchr/testify/require"

"github.com/launchdarkly/ldcli/internal/analytics"
"github.com/launchdarkly/ldcli/internal/config"
"github.com/launchdarkly/ldcli/internal/resources"
)

var StubbedSuccessResponse = `{
Expand All @@ -24,6 +26,7 @@ func CallCmd(
args []string,
) ([]byte, error) {
rootCmd, err := NewRootCommand(
config.NewService(&resources.MockClient{}),
trackerFn,
clients,
"test",
Expand Down
26 changes: 23 additions & 3 deletions cmd/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"sort"
"strings"

errs "github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"gopkg.in/yaml.v3"
Expand Down Expand Up @@ -38,10 +39,10 @@ func (cmd ConfigCmd) HelpCalled() bool {
return cmd.helpCalled
}

func NewConfigCmd(analyticsTrackerFn analytics.TrackerFn) *ConfigCmd {
func NewConfigCmd(service config.Service, analyticsTrackerFn analytics.TrackerFn) *ConfigCmd {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pass in the service so we can mock its HTTP client in tests.

cmd := &cobra.Command{
Long: "View and modify specific configuration values",
RunE: run(),
RunE: run(service),
Short: "View and modify specific configuration values",
Use: "config",
PreRun: func(cmd *cobra.Command, args []string) {
Expand Down Expand Up @@ -93,7 +94,7 @@ func NewConfigCmd(analyticsTrackerFn analytics.TrackerFn) *ConfigCmd {
return &configCmd
}

func run() func(*cobra.Command, []string) error {
func run(service config.Service) func(*cobra.Command, []string) error {
return func(cmd *cobra.Command, args []string) error {
switch {
case viper.GetBool(ListFlag):
Expand Down Expand Up @@ -146,6 +147,25 @@ func run() func(*cobra.Command, []string) error {
}
}

var updatingAccessToken bool
for _, f := range newFields {
if f == cliflags.AccessTokenFlag {
updatingAccessToken = true
break
}
}
if updatingAccessToken {
if !service.VerifyAccessToken(
rawConfig[cliflags.AccessTokenFlag].(string),
viper.GetString(cliflags.BaseURIFlag),
) {
errorMessage := fmt.Sprintf("%s is invalid. ", cliflags.AccessTokenFlag)
errorMessage += errors.AccessTokenInvalidErrMessage(viper.GetString(cliflags.BaseURIFlag))
err := errs.New(errorMessage)
return errors.NewError(output.CmdOutputError(viper.GetString(cliflags.OutputFlag), err))
}
}

configFile, err := config.NewConfig(rawConfig)
if err != nil {
return errors.NewError(output.CmdOutputError(viper.GetString(cliflags.OutputFlag), err))
Expand Down
7 changes: 5 additions & 2 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
package cmd

import (
"errors"
"fmt"
"log"
"os"
"path/filepath"
"strings"

"github.com/google/uuid"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"

Expand Down Expand Up @@ -68,6 +68,7 @@ func (cmd RootCmd) Execute() error {
}

func NewRootCommand(
configService config.Service,
analyticsTrackerFn analytics.TrackerFn,
clients APIClients,
version string,
Expand Down Expand Up @@ -182,7 +183,7 @@ func NewRootCommand(
return nil, err
}

configCmd := configcmd.NewConfigCmd(analyticsTrackerFn)
configCmd := configcmd.NewConfigCmd(configService, analyticsTrackerFn)
cmd.AddCommand(configCmd.Cmd())
cmd.AddCommand(NewQuickStartCmd(analyticsTrackerFn, clients.EnvironmentsClient, clients.FlagsClient))
cmd.AddCommand(resourcecmd.NewResourcesCmd())
Expand Down Expand Up @@ -213,10 +214,12 @@ func Execute(version string) {
ProjectsClient: projects.NewClient(version),
ResourcesClient: resources.NewClient(version),
}
configService := config.NewService(resources.NewClient(version))
trackerFn := analytics.ClientFn{
ID: uuid.New().String(),
}
rootCmd, err := NewRootCommand(
configService,
trackerFn.Tracker(version),
clients,
version,
Expand Down
5 changes: 3 additions & 2 deletions cmd/validators/validators.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package validators

import (
"errors"
"fmt"
"net/url"
"strings"

"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/spf13/viper"
Expand Down Expand Up @@ -46,7 +46,8 @@ func CmdError(err error, commandPath string, baseURI string) error {
if strings.Contains(err.Error(), cliflags.AccessTokenFlag) {
errorMessage += "\n\n"
if baseURI != "" {
errorMessage += fmt.Sprintf("Go to %s/settings/authorization to create an access token.\n", baseURI)
errorMessage += errs.AccessTokenInvalidErrMessage(baseURI)
errorMessage += "\n"
}
errorMessage += fmt.Sprintf("Use `ldcli config --set %s <value>` to configure the value to persist across CLI commands.\n\n", cliflags.AccessTokenFlag)
} else {
Expand Down
2 changes: 1 addition & 1 deletion cmd/validators/validators_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package validators_test

import (
"errors"
"testing"

"github.com/pkg/errors"
"github.com/stretchr/testify/assert"

"github.com/launchdarkly/ldcli/cmd/validators"
Expand Down
29 changes: 29 additions & 0 deletions internal/config/config_service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package config

import (
"fmt"

"github.com/launchdarkly/ldcli/internal/resources"
)

type Service struct {
client resources.Client
}

func NewService(client resources.Client) Service {
return Service{
client: client,
}
}

// VerifyAccessToken is true if the given access token is valid to make API requests.
func (s Service) VerifyAccessToken(accessToken string, baseURI string) bool {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could move more of the config --set code into the config domain if it would make the cmd more readable.

path := fmt.Sprintf(
"%s/api/v2/account",
baseURI,
)

_, err := s.client.MakeRequest(accessToken, "HEAD", path, "application/json", nil, nil)

return err == nil
}
27 changes: 26 additions & 1 deletion internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@ package config_test

import (
"fmt"
"github.com/launchdarkly/ldcli/internal/config"
"net/http"
"testing"

"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/launchdarkly/ldcli/internal/config"
"github.com/launchdarkly/ldcli/internal/resources"
)

func TestNewConfig(t *testing.T) {
Expand Down Expand Up @@ -132,3 +136,24 @@ func TestNewConfig(t *testing.T) {
})
})
}

func TestService_VerifyAccessToken(t *testing.T) {
t.Run("is valid with a valid access token", func(t *testing.T) {
service := config.NewService(&resources.MockClient{})

isValid := service.VerifyAccessToken("valid-access-token", "http://test.com")

assert.True(t, isValid)
})

t.Run("is invalid with an invalid access token", func(t *testing.T) {
service := config.NewService(&resources.MockClient{
StatusCode: http.StatusUnauthorized,
Err: errors.New("invalid access token"),
})

isValid := service.VerifyAccessToken("invalid-access-token", "http://test.com")

assert.False(t, isValid)
})
}
4 changes: 4 additions & 0 deletions internal/errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,7 @@ func normalizeUnauthorizedJSON() ([]byte, error) {

return errMsg, nil
}

func AccessTokenInvalidErrMessage(baseURI string) string {
return fmt.Sprintf("Go to %s/settings/authorization to create an access token.", baseURI)
}
2 changes: 1 addition & 1 deletion internal/errors/errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ package errors_test

import (
"encoding/json"
"errors"
"testing"

ldapi "github.com/launchdarkly/api-client-go/v14"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

Expand Down
15 changes: 12 additions & 3 deletions internal/resources/mock_client.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package resources

import "net/url"
import (
"net/http"
"net/url"
)

type MockClient struct {
Input []byte
Response []byte
Err error
Input []byte
Response []byte
StatusCode int
}

var _ Client = &MockClient{}
Expand All @@ -16,5 +21,9 @@ func (c *MockClient) MakeRequest(
) ([]byte, error) {
c.Input = data

if c.StatusCode > http.StatusBadRequest {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added this for stubbing the HTTP client response.

return c.Response, c.Err
}

return c.Response, nil
}
Loading