From d2ddc13a0431808e886e241dd42e99bb81e541ca Mon Sep 17 00:00:00 2001 From: filip Date: Mon, 9 Sep 2024 12:55:32 +0200 Subject: [PATCH] Rename and add subcommands --- internal/{login => auth}/auth.go | 93 ++++++++++++++++----- internal/client/deb_client.go | 5 ++ internal/client/testdata/deb_client_mock.go | 4 + internal/cmd/auth/auth.go | 26 ++++++ internal/cmd/{ => auth}/login/login.go | 8 +- internal/cmd/auth/logout/logout.go | 38 +++++++++ internal/cmd/auth/token/token.go | 40 +++++++++ internal/cmd/root/root.go | 4 +- internal/cmd/root/root_test.go | 8 +- internal/file/finder_test.go | 4 + internal/login/login.go | 10 --- internal/login/token.go | 67 --------------- internal/upload/uploader_test.go | 4 + internal/wire/cli_container.go | 12 +-- 14 files changed, 207 insertions(+), 116 deletions(-) rename internal/{login => auth}/auth.go (52%) create mode 100644 internal/cmd/auth/auth.go rename internal/cmd/{ => auth}/login/login.go (72%) create mode 100644 internal/cmd/auth/logout/logout.go create mode 100644 internal/cmd/auth/token/token.go delete mode 100644 internal/login/login.go delete mode 100644 internal/login/token.go diff --git a/internal/login/auth.go b/internal/auth/auth.go similarity index 52% rename from internal/login/auth.go rename to internal/auth/auth.go index b1abb817..444937c3 100644 --- a/internal/login/auth.go +++ b/internal/auth/auth.go @@ -1,52 +1,97 @@ -package login +package auth import ( "context" - "crypto/sha256" - "encoding/base64" "fmt" - // "github.com/debricked/cli/internal/client" + "github.com/debricked/cli/internal/client" + "github.com/zalando/go-keyring" "log" - "math/rand" "net/http" "os/exec" "runtime" - "strings" - "time" "golang.org/x/oauth2" ) type IAuthenticator interface { - Authenticate() (*oauth2.Token, error) + Authenticate() error + Logout() error + Token() (*oauth2.Token, error) } type Authenticator struct { - ClientID string - Scopes []string + ClientID string + Scopes []string + Client client.IDebClient + SecretClient ISecretClient } -const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" +type ISecretClient interface { + Set(string, string) error + Get(string) (string, error) + Delete(string) error +} + +type DebrickedSecretClient struct { + User string +} + +func (dsc DebrickedSecretClient) Set(service, secret string) error { + return keyring.Set(service, dsc.User, secret) +} + +func (dsc DebrickedSecretClient) Get(service string) (string, error) { + return keyring.Get(service, dsc.User) +} + +func (dsc DebrickedSecretClient) Delete(service string) error { + return keyring.Delete(service, dsc.User) +} + +func NewDebrickedAuthenticator(client client.IDebClient) Authenticator { + return Authenticator{ + ClientID: "01919462-7d6e-78e8-aa24-ba779213c90f", + Scopes: []string{"select", "profile", "basicRepo"}, + Client: client, + SecretClient: DebrickedSecretClient{ + User: "DebrickedCLI", + }, + } +} -func generateRandomString(length int) string { - seed := rand.NewSource(time.Now().UnixNano()) - r := rand.New(seed) +func (a Authenticator) Logout() error { + err := a.SecretClient.Delete("DebrickedRefreshToken") + if err != nil { + return err + } + err = a.SecretClient.Delete("DebrickedAccessToken") + return err +} - b := make([]byte, length) - for i := range b { - b[i] = charset[r.Intn(len(charset))] +func (a Authenticator) Token() (*oauth2.Token, error) { + refreshToken, err := a.SecretClient.Get("DebrickedRefreshToken") + if err != nil { + return nil, err } - return string(b) + accessToken, err := a.SecretClient.Get("DebrickedAccessToken") + if err != nil { + return nil, err + } + return &oauth2.Token{ + RefreshToken: refreshToken, + TokenType: "jwt", + AccessToken: accessToken, + }, nil } -func (a Authenticator) Authenticate() (*oauth2.Token, error) { +func (a Authenticator) Authenticate() error { // Set up OAuth2 configuration config := &oauth2.Config{ ClientID: a.ClientID, ClientSecret: "", Endpoint: oauth2.Endpoint{ - AuthURL: "https://debricked.com/app/oauth/authorize", - TokenURL: "https://debricked.com/app/oauth/token", + AuthURL: a.Client.Host() + "/app/oauth/authorize", + TokenURL: a.Client.Host() + "/app/oauth/token", }, RedirectURL: "http://localhost:9096/callback", Scopes: a.Scopes, @@ -102,9 +147,11 @@ func (a Authenticator) Authenticate() (*oauth2.Token, error) { oauth2.VerifierOption(codeVerifier), ) if err != nil { - return nil, err + return err } - return token, nil + a.SecretClient.Set("DebrickedRefreshToken", token.RefreshToken) + a.SecretClient.Set("DebrickedAccessToken", token.AccessToken) + return nil } func openBrowser(url string) error { diff --git a/internal/client/deb_client.go b/internal/client/deb_client.go index 0a61f6af..dc98bbd7 100644 --- a/internal/client/deb_client.go +++ b/internal/client/deb_client.go @@ -22,6 +22,7 @@ type IDebClient interface { Get(uri string, format string) (*http.Response, error) SetAccessToken(accessToken *string) IsEnterpriseCustomer(silent bool) bool + Host() string } type DebClient struct { @@ -45,6 +46,10 @@ func NewDebClient(accessToken *string, httpClient IClient) *DebClient { } } +func (debClient *DebClient) Host() string { + return *debClient.host +} + func (debClient *DebClient) Post(uri string, contentType string, body *bytes.Buffer, timeout int) (*http.Response, error) { if timeout > 0 { return postWithTimeout(uri, debClient, contentType, body, true, timeout) diff --git a/internal/client/testdata/deb_client_mock.go b/internal/client/testdata/deb_client_mock.go index d897e3db..60feb184 100644 --- a/internal/client/testdata/deb_client_mock.go +++ b/internal/client/testdata/deb_client_mock.go @@ -34,6 +34,10 @@ func NewDebClientMock() *DebClientMock { } } +func (mock *DebClientMock) Host() string { + return "debricked.com" +} + func (mock *DebClientMock) Get(uri string, format string) (*http.Response, error) { response, err := mock.popResponse(mock.RemoveQueryParamsFromUri(uri)) diff --git a/internal/cmd/auth/auth.go b/internal/cmd/auth/auth.go new file mode 100644 index 00000000..6dea5f34 --- /dev/null +++ b/internal/cmd/auth/auth.go @@ -0,0 +1,26 @@ +package auth + +import ( + "github.com/debricked/cli/internal/auth" + "github.com/debricked/cli/internal/cmd/auth/login" + "github.com/debricked/cli/internal/cmd/auth/logout" + "github.com/debricked/cli/internal/cmd/auth/token" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +func NewAuthCmd(authenticator auth.IAuthenticator) *cobra.Command { + cmd := &cobra.Command{ + Use: "auth", + Short: "Debricked authentication.", + Long: `Debricked service authentication.`, + PreRun: func(cmd *cobra.Command, _ []string) { + _ = viper.BindPFlags(cmd.Flags()) + }, + } + cmd.AddCommand(login.NewLoginCmd(authenticator)) + cmd.AddCommand(logout.NewLogoutCmd(authenticator)) + cmd.AddCommand(token.NewTokenCmd(authenticator)) + + return cmd +} diff --git a/internal/cmd/login/login.go b/internal/cmd/auth/login/login.go similarity index 72% rename from internal/cmd/login/login.go rename to internal/cmd/auth/login/login.go index b63f85ff..427a5204 100644 --- a/internal/cmd/login/login.go +++ b/internal/cmd/auth/login/login.go @@ -2,13 +2,13 @@ package login import ( "fmt" - "github.com/debricked/cli/internal/login" + "github.com/debricked/cli/internal/auth" "github.com/fatih/color" "github.com/spf13/cobra" "github.com/spf13/viper" ) -func NewLoginCmd(authenticator login.IAuthenticator) *cobra.Command { +func NewLoginCmd(authenticator auth.IAuthenticator) *cobra.Command { cmd := &cobra.Command{ Use: "login", Short: "Authenticate debricked user", @@ -22,9 +22,9 @@ func NewLoginCmd(authenticator login.IAuthenticator) *cobra.Command { return cmd } -func RunE(a login.IAuthenticator) func(_ *cobra.Command, args []string) error { +func RunE(a auth.IAuthenticator) func(_ *cobra.Command, args []string) error { return func(cmd *cobra.Command, _ []string) error { - _, err := login.AuthToken() + _, err := a.Authenticate() if err != nil { return err } diff --git a/internal/cmd/auth/logout/logout.go b/internal/cmd/auth/logout/logout.go new file mode 100644 index 00000000..c693c79f --- /dev/null +++ b/internal/cmd/auth/logout/logout.go @@ -0,0 +1,38 @@ +package logout + +import ( + "fmt" + "github.com/debricked/cli/internal/auth" + "github.com/fatih/color" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +func NewLogoutCmd(authenticator auth.IAuthenticator) *cobra.Command { + cmd := &cobra.Command{ + Use: "logout", + Short: "Logout debricked user", + Long: `Remove cached credentials to logout debricked user.`, + PreRun: func(cmd *cobra.Command, _ []string) { + _ = viper.BindPFlags(cmd.Flags()) + }, + RunE: RunE(authenticator), + } + + return cmd +} + +func RunE(a auth.IAuthenticator) func(_ *cobra.Command, args []string) error { + return func(cmd *cobra.Command, _ []string) error { + err := a.Logout() + if err != nil { + return err + } + fmt.Printf( + "%s Successfully removed credentials", + color.GreenString("✔"), + ) + + return nil + } +} diff --git a/internal/cmd/auth/token/token.go b/internal/cmd/auth/token/token.go new file mode 100644 index 00000000..68175e7a --- /dev/null +++ b/internal/cmd/auth/token/token.go @@ -0,0 +1,40 @@ +package token + +import ( + "fmt" + "github.com/debricked/cli/internal/auth" + + "github.com/fatih/color" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +func NewTokenCmd(authenticator auth.IAuthenticator) *cobra.Command { + cmd := &cobra.Command{ + Use: "token", + Short: "Retrieve access token", + Long: `Retrieve access token for currently logged in Debricked user.`, + PreRun: func(cmd *cobra.Command, _ []string) { + _ = viper.BindPFlags(cmd.Flags()) + }, + RunE: RunE(authenticator), + } + + return cmd +} + +func RunE(a auth.IAuthenticator) func(_ *cobra.Command, args []string) error { + return func(cmd *cobra.Command, _ []string) error { + token, err := a.Token() + if err != nil { + return err + } + fmt.Printf( + "Refresh Token = %s\nAccess Token = %s\n", + color.BlueString(token.RefreshToken), + color.BlueString(token.AccessToken), + ) + + return nil + } +} diff --git a/internal/cmd/root/root.go b/internal/cmd/root/root.go index 6e9efee6..a98177f3 100644 --- a/internal/cmd/root/root.go +++ b/internal/cmd/root/root.go @@ -1,10 +1,10 @@ package root import ( + "github.com/debricked/cli/internal/cmd/auth" "github.com/debricked/cli/internal/cmd/callgraph" "github.com/debricked/cli/internal/cmd/files" "github.com/debricked/cli/internal/cmd/fingerprint" - "github.com/debricked/cli/internal/cmd/login" "github.com/debricked/cli/internal/cmd/report" "github.com/debricked/cli/internal/cmd/resolve" "github.com/debricked/cli/internal/cmd/scan" @@ -48,7 +48,7 @@ Read more: https://docs.debricked.com/product/administration/generate-access-tok rootCmd.AddCommand(fingerprint.NewFingerprintCmd(container.Fingerprinter())) rootCmd.AddCommand(resolve.NewResolveCmd(container.Resolver())) rootCmd.AddCommand(callgraph.NewCallgraphCmd(container.CallgraphGenerator())) - rootCmd.AddCommand(login.NewLoginCmd(container.Authenticator())) + rootCmd.AddCommand(auth.NewAuthCmd(container.Authenticator())) rootCmd.CompletionOptions.DisableDefaultCmd = true diff --git a/internal/cmd/root/root_test.go b/internal/cmd/root/root_test.go index a9a49731..cb6ce145 100644 --- a/internal/cmd/root/root_test.go +++ b/internal/cmd/root/root_test.go @@ -11,9 +11,13 @@ import ( func TestNewRootCmd(t *testing.T) { cmd := NewRootCmd("v0.0.0", wire.GetCliContainer()) commands := cmd.Commands() - nbrOfCommands := 6 + nbrOfCommands := 7 if len(commands) != nbrOfCommands { - t.Errorf("failed to assert that there were %d sub commands connected", nbrOfCommands) + t.Errorf( + "failed to assert that there were %d sub commands connected (was %d)", + nbrOfCommands, + len(commands), + ) } flags := cmd.PersistentFlags() diff --git a/internal/file/finder_test.go b/internal/file/finder_test.go index 991caa90..381e9d87 100644 --- a/internal/file/finder_test.go +++ b/internal/file/finder_test.go @@ -20,6 +20,10 @@ import ( type debClientMock struct{} +func (mock *debClientMock) Host() string { + return "debricked.com" +} + func (mock *debClientMock) Post(_ string, _ string, _ *bytes.Buffer, _ int) (*http.Response, error) { return &http.Response{}, nil } diff --git a/internal/login/login.go b/internal/login/login.go deleted file mode 100644 index 0d75fa1d..00000000 --- a/internal/login/login.go +++ /dev/null @@ -1,10 +0,0 @@ -package login - -func AuthToken() (string, error) { - tokenSource := GetDebrickedTokenSource() - token, err := tokenSource.Token() - if err != nil { - return "", err - } - return token.AccessToken, nil -} diff --git a/internal/login/token.go b/internal/login/token.go deleted file mode 100644 index 494fde82..00000000 --- a/internal/login/token.go +++ /dev/null @@ -1,67 +0,0 @@ -package login - -import ( - "github.com/zalando/go-keyring" - - "golang.org/x/oauth2" -) - -type SecretClient interface { - Set(string, string) error - Get(string) (string, error) -} - -type DebrickedSecretClient struct { - User string -} - -type DebrickedTokenSource struct { - SecretClient SecretClient -} - -func (dsc DebrickedSecretClient) Set(service, secret string) error { - return keyring.Set(service, dsc.User, secret) -} - -func (dsc DebrickedSecretClient) Get(service string) (string, error) { - return keyring.Get(service, dsc.User) -} - -func GetDebrickedTokenSource() oauth2.TokenSource { - return DebrickedTokenSource{ - SecretClient: DebrickedSecretClient{ - User: "DebrickedCLI", - }, - } -} - -func (dts DebrickedTokenSource) Token() (*oauth2.Token, error) { - refreshToken, err := dts.SecretClient.Get("DebrickedRefreshToken") - if err != nil { - if err == keyring.ErrNotFound { - // refreshToken is not yet set, initialize authorization - authenticator := Authenticator{ - ClientID: "01919462-7d6e-78e8-aa24-ba779213c90f", - Scopes: []string{"select", "profile", "basicRepo"}, - } - token, err := authenticator.Authenticate() - if err != nil { - return nil, err - } - dts.SecretClient.Set("DebrickedRefreshToken", token.RefreshToken) - dts.SecretClient.Set("DebrickedAccessToken", token.AccessToken) - } else { - return nil, err - } - } - accessToken, err := dts.SecretClient.Get("DebrickedAccessToken") - if err != nil { - accessToken = "" - } - // TODO: Parse expiry date - return &oauth2.Token{ - RefreshToken: refreshToken, - TokenType: "jwt", - AccessToken: accessToken, - }, nil -} diff --git a/internal/upload/uploader_test.go b/internal/upload/uploader_test.go index 5d3fb970..22483ba4 100644 --- a/internal/upload/uploader_test.go +++ b/internal/upload/uploader_test.go @@ -90,6 +90,10 @@ func TestUploadPollingError(t *testing.T) { type debClientMock struct{} +func (mock *debClientMock) Host() string { + return "debricked.com" +} + func (mock *debClientMock) Post(uri string, _ string, _ *bytes.Buffer, _ int) (*http.Response, error) { res := &http.Response{ Status: "", diff --git a/internal/wire/cli_container.go b/internal/wire/cli_container.go index c5a8a193..f232f16a 100644 --- a/internal/wire/cli_container.go +++ b/internal/wire/cli_container.go @@ -3,6 +3,7 @@ package wire import ( "fmt" + "github.com/debricked/cli/internal/auth" "github.com/debricked/cli/internal/callgraph" callgraphStrategy "github.com/debricked/cli/internal/callgraph/strategy" "github.com/debricked/cli/internal/ci" @@ -10,7 +11,6 @@ import ( "github.com/debricked/cli/internal/file" "github.com/debricked/cli/internal/fingerprint" "github.com/debricked/cli/internal/io" - "github.com/debricked/cli/internal/login" licenseReport "github.com/debricked/cli/internal/report/license" vulnerabilityReport "github.com/debricked/cli/internal/report/vulnerability" "github.com/debricked/cli/internal/resolution" @@ -92,11 +92,7 @@ func (cc *CliContainer) wire() error { cc.licenseReporter = licenseReport.Reporter{DebClient: cc.debClient} cc.vulnerabilityReporter = vulnerabilityReport.Reporter{DebClient: cc.debClient} - - cc.authenticator = login.Authenticator{ - ClientID: "01919462-7d6e-78e8-aa24-ba779213c90f", - Scopes: []string{"select", "profile", "basicRepo"}, - } + cc.authenticator = auth.NewDebrickedAuthenticator(cc.debClient) return nil } @@ -118,7 +114,7 @@ type CliContainer struct { callgraph callgraph.IGenerator cgScheduler callgraph.IScheduler cgStrategyFactory callgraphStrategy.IFactory - authenticator login.IAuthenticator + authenticator auth.IAuthenticator } func (cc *CliContainer) DebClient() client.IDebClient { @@ -153,7 +149,7 @@ func (cc *CliContainer) Fingerprinter() fingerprint.IFingerprint { return cc.fingerprinter } -func (cc *CliContainer) Authenticator() login.IAuthenticator { +func (cc *CliContainer) Authenticator() auth.IAuthenticator { return cc.authenticator }