diff --git a/cmd/login.go b/cmd/login.go index bd24415f..2180504e 100644 --- a/cmd/login.go +++ b/cmd/login.go @@ -219,7 +219,7 @@ func oauth2login( // Authorization redirect callback from OAuth2 auth flow. // Handles both implicit and authorization code flow callbackHandler := func(w http.ResponseWriter, r *http.Request) { - log.Printf("Callback: %s\n", r.URL) + log.Printf("Callback received on: %s\n", r.URL.Path) if formErr := r.FormValue("error"); formErr != "" { handleErr(w, fmt.Sprintf("%s: %s", formErr, r.FormValue("error_description"))) @@ -276,7 +276,8 @@ func oauth2login( opts = append(opts, oauth2.SetAuthURLParam("code_challenge_method", "S256")) url = oauth2conf.AuthCodeURL(stateNonce, opts...) - fmt.Printf("Performing %s flow login: %s\n", "authorization_code", url) + authBaseURL := strings.SplitN(url, "?", 2)[0] + fmt.Printf("Performing %s flow login: %s\n", "authorization_code", authBaseURL) time.Sleep(1 * time.Second) ssoAuthFlow(url, ssoLaunchBrowser) go func() { @@ -293,8 +294,7 @@ func oauth2login( ctx, cancel := context.WithTimeout(ctx, 1*time.Second) defer cancel() _ = srv.Shutdown(ctx) - log.Printf("Token: %s\n", tokenString) - log.Printf("Refresh Token: %s\n", refreshToken) + return tokenString, refreshToken } diff --git a/cmd/logout.go b/cmd/logout.go index 37351fb1..b1c638cc 100644 --- a/cmd/logout.go +++ b/cmd/logout.go @@ -7,7 +7,6 @@ import ( "github.com/microcks/microcks-cli/pkg/config" "github.com/microcks/microcks-cli/pkg/connectors" - "github.com/microcks/microcks-cli/pkg/errors" "github.com/spf13/cobra" ) @@ -29,29 +28,40 @@ microcks logout dev-context`, os.Exit(1) } - context := args[0] - localCfg, err := config.ReadLocalConfig(globalClientOpts.ConfigPath) - errors.CheckError(err) - if localCfg == nil { - log.Fatalf("Nothing to logout from") - } - - // Remove authToken - ok := localCfg.RemoveToken(context) - if !ok { - log.Fatalf("Context %s does not exist", context) - } - - err = config.ValidateLocalConfig(*localCfg) + target := args[0] + err := logoutContext(target, globalClientOpts.ConfigPath) if err != nil { - log.Fatalf("Error in loging out: %s", err) + log.Fatal(err) } - err = config.WriteLocalConfig(*localCfg, globalClientOpts.ConfigPath) - errors.CheckError(err) - - fmt.Printf("Logged out from '%s'\n", context) + fmt.Printf("Logged out from '%s'\n", target) }, } return logoutCmd } + +func logoutContext(target, configPath string) error { + localCfg, err := config.ReadLocalConfig(configPath) + if err != nil { + return err + } + if localCfg == nil { + return fmt.Errorf("Nothing to logout from") + } + + userName := target + if ctx, err := localCfg.ResolveContext(target); err == nil { + userName = ctx.User.Name + } + + if ok := localCfg.RemoveToken(userName); !ok { + return fmt.Errorf("Context %s does not exist", target) + } + + err = config.ValidateLocalConfig(*localCfg) + if err != nil { + return fmt.Errorf("Error in loging out: %s", err) + } + + return config.WriteLocalConfig(*localCfg, configPath) +} diff --git a/cmd/logout_test.go b/cmd/logout_test.go new file mode 100644 index 00000000..2801f445 --- /dev/null +++ b/cmd/logout_test.go @@ -0,0 +1,69 @@ +package cmd + +import ( + "path/filepath" + "testing" + + "github.com/microcks/microcks-cli/pkg/config" + "github.com/stretchr/testify/require" +) + +func TestLogoutContextResolvesNamedContextUser(t *testing.T) { + configPath := filepath.Join(t.TempDir(), "config") + server := "https://microcks.example" + + localCfg := config.LocalConfig{ + CurrentContext: "staging", + Contexts: []config.ContextRef{ + {Name: "staging", Server: server, User: server}, + }, + Servers: []config.Server{ + {Server: server, KeycloakEnable: true}, + }, + Users: []config.User{ + {Name: server, AuthToken: "access-token", RefreshToken: "refresh-token"}, + }, + } + require.NoError(t, config.WriteLocalConfig(localCfg, configPath)) + + require.NoError(t, logoutContext("staging", configPath)) + + updated, err := config.ReadLocalConfig(configPath) + require.NoError(t, err) + require.NotNil(t, updated) + + user, err := updated.GetUser(server) + require.NoError(t, err) + require.Empty(t, user.AuthToken) + require.Empty(t, user.RefreshToken) +} + +func TestLogoutContextStillAcceptsStoredUserName(t *testing.T) { + configPath := filepath.Join(t.TempDir(), "config") + server := "https://microcks.example" + + localCfg := config.LocalConfig{ + CurrentContext: "staging", + Contexts: []config.ContextRef{ + {Name: "staging", Server: server, User: server}, + }, + Servers: []config.Server{ + {Server: server, KeycloakEnable: true}, + }, + Users: []config.User{ + {Name: server, AuthToken: "access-token", RefreshToken: "refresh-token"}, + }, + } + require.NoError(t, config.WriteLocalConfig(localCfg, configPath)) + + require.NoError(t, logoutContext(server, configPath)) + + updated, err := config.ReadLocalConfig(configPath) + require.NoError(t, err) + require.NotNil(t, updated) + + user, err := updated.GetUser(server) + require.NoError(t, err) + require.Empty(t, user.AuthToken) + require.Empty(t, user.RefreshToken) +} diff --git a/pkg/config/config.go b/pkg/config/config.go index e5ff9a98..2c473f8b 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -23,7 +23,8 @@ import ( "net/http/httputil" "os" "path/filepath" - strings "strings" + "regexp" + "strings" ) var ( @@ -37,6 +38,13 @@ var ( ConfigPath = filepath.Join(os.Getenv("HOME"), ".microcks-cli", "config.yaml") ) +var sensitiveHeaderPattern = regexp.MustCompile( + `(?im)^(Authorization:\s*)(Bearer\s+)?(.+)$`, +) +var sensitiveParamPattern = regexp.MustCompile( + `(?i)(access_token|refresh_token|id_token|code)=([^&\s]+)`, +) + // CreateTLSConfig wraps the creation of tls.Config object for use with HTTP Client for example. func CreateTLSConfig() *tls.Config { tlsConfig := &tls.Config{} @@ -76,7 +84,7 @@ func DumpRequestIfRequired(name string, req *http.Request, body bool) { if err != nil { fmt.Println("Got error while dumping request out") } - fmt.Printf("%s", dump) + fmt.Printf("%s", redactSensitiveContent(string(dump))) } } @@ -88,9 +96,16 @@ func DumpResponseIfRequired(name string, resp *http.Response, body bool) { if err != nil { fmt.Println("Got error while dumping response") } - fmt.Printf("%s", dump) + fmt.Printf("%s", redactSensitiveContent(string(dump))) if body { fmt.Println("") } } } + +// redactSensitiveContent masks OAuth tokens and credentials in HTTP dump output. +func redactSensitiveContent(dump string) string { + redacted := sensitiveHeaderPattern.ReplaceAllString(dump, "${1}[REDACTED]") + redacted = sensitiveParamPattern.ReplaceAllString(redacted, "${1}=[REDACTED]") + return redacted +}