Skip to content

Commit

Permalink
fix(login): login now passively combines environmental config and c…
Browse files Browse the repository at this point in the history
…onfig file data.

chore(deps): update `keyfactor-go-client`

Signed-off-by: sbailey <[email protected]>
  • Loading branch information
spbsoluble committed Aug 20, 2024
1 parent 7c37cde commit 693ae72
Show file tree
Hide file tree
Showing 5 changed files with 260 additions and 149 deletions.
274 changes: 163 additions & 111 deletions cmd/login_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,43 @@ package cmd
import (
"encoding/json"
"fmt"
"github.com/joho/godotenv"
"github.com/stretchr/testify/assert"
"os"
"os/user"
"path/filepath"
"regexp"
"testing"

"github.com/joho/godotenv"
"github.com/stretchr/testify/assert"
)

func extractAndParseJSON(data string) ([]map[string]interface{}, error) {
// Improved regular expression to match JSON objects or arrays
jsonPattern := regexp.MustCompile(`\{[^{}]*\}|\[[^\[\]]*\]`)

// Find all JSON-like strings within the non-structured string
jsonStrings := jsonPattern.FindAllString(data, -1)

var results []map[string]interface{}
for _, jsonString := range jsonStrings {
var parsedData map[string]interface{}
err := json.Unmarshal([]byte(jsonString), &parsedData)
if err != nil {
// If the JSON is not a map, try unmarshalling into a slice of interfaces
var parsedSlice []interface{}
err := json.Unmarshal([]byte(jsonString), &parsedSlice)
if err != nil {
// Skip non-JSON matches
continue
}
parsedData = map[string]interface{}{"data": parsedSlice}
}
results = append(results, parsedData)
}

return results, nil
}

func Test_LoginHelpCmd(t *testing.T) {
// Test root help
testCmd := RootCmd
Expand Down Expand Up @@ -67,10 +96,12 @@ func Test_LoginCmdConfigParams(t *testing.T) {
testCmd := RootCmd
// test
testCmd.SetArgs([]string{"stores", "list", "--exp", "--config", "$HOME/.keyfactor/extra_config.json"})
output := captureOutput(func() {
err := testCmd.Execute()
assert.NoError(t, err)
})
output := captureOutput(
func() {
err := testCmd.Execute()
assert.NoError(t, err)
},
)
t.Logf("output: %s", output)
var stores []string
if err := json.Unmarshal([]byte(output), &stores); err != nil {
Expand All @@ -82,34 +113,38 @@ func Test_LoginCmdConfigParams(t *testing.T) {
}

func testLogout(t *testing.T) {
t.Run(fmt.Sprintf("Logout"), func(t *testing.T) {
testCmd := RootCmd
// test
testCmd.SetArgs([]string{"logout"})
output := captureOutput(func() {
err := testCmd.Execute()
assert.NoError(t, err)
})
t.Logf("output: %s", output)
t.Run(
fmt.Sprintf("Logout"), func(t *testing.T) {
testCmd := RootCmd
// test
testCmd.SetArgs([]string{"logout"})
output := captureOutput(
func() {
err := testCmd.Execute()
assert.NoError(t, err)
},
)
t.Logf("output: %s", output)

assert.Contains(t, output, "Logged out successfully!")
assert.Contains(t, output, "Logged out successfully!")

// Get the current user's information
currentUser, err := user.Current()
if err != nil {
fmt.Println("Error:", err)
return
}
// Get the current user's information
currentUser, err := user.Current()
if err != nil {
fmt.Println("Error:", err)
return
}

// Define the path to the file in the user's home directory
filePath := filepath.Join(currentUser.HomeDir, ".keyfactor/command_config.json")
_, err = os.Stat(filePath)
// Define the path to the file in the user's home directory
filePath := filepath.Join(currentUser.HomeDir, ".keyfactor/command_config.json")
_, err = os.Stat(filePath)

// Test that the config file does not exist
if _, fErr := os.Stat(filePath); !os.IsNotExist(fErr) {
t.Errorf("Config file %s still exists, please remove", filePath)
}
})
// Test that the config file does not exist
if _, fErr := os.Stat(filePath); !os.IsNotExist(fErr) {
t.Errorf("Config file %s still exists, please remove", filePath)
}
},
)

}

Expand All @@ -120,26 +155,31 @@ func testConfigValid(t *testing.T) {
//t.Logf("envUsername: %s", envUsername)
//t.Logf("envPassword: %s", envPassword)
t.Logf("Attempting to run `store-types list`")
t.Run(fmt.Sprintf("List store types"), func(t *testing.T) {
testCmd := RootCmd
t.Log("Setting args")
testCmd.SetArgs([]string{"store-types", "list"})
t.Logf("args: %v", testCmd.Args)
t.Log("Capturing output")
output := captureOutput(func() {
tErr := testCmd.Execute()
assert.NoError(t, tErr)
})
t.Logf("output: %s", output)

var storeTypes []map[string]interface{}
if err := json.Unmarshal([]byte(output), &storeTypes); err != nil {
t.Fatalf("Error unmarshalling JSON: %v", err)
}
t.Run(
fmt.Sprintf("List store types"), func(t *testing.T) {
testCmd := RootCmd
t.Log("Setting args")
testCmd.SetArgs([]string{"store-types", "list"})
t.Logf("args: %v", testCmd.Args)
t.Log("Capturing output")
output := captureOutput(
func() {
tErr := testCmd.Execute()
assert.NoError(t, tErr)
},
)
t.Logf("output: %s", output)

jsonResp, jErr := extractAndParseJSON(output)
if jErr != nil {
t.Fatalf("Error parsing JSON: %v", jErr)
}

// Verify that the length of the response is greater than 0
assert.True(t, len(storeTypes) >= 0, "Expected non-empty list of store types")
})
// Verify that the length of the response is greater than 0
assert.True(t, len(jsonResp) >= 0, "Expected non-empty list of store types")
// parse only what fits JSON regex from output
},
)
}

func testConfigExists(t *testing.T, filePath string, allowExist bool) {
Expand All @@ -149,77 +189,89 @@ func testConfigExists(t *testing.T, filePath string, allowExist bool) {
} else {
testName = "Config file does not exist"
}
t.Run(fmt.Sprintf(testName), func(t *testing.T) {
_, fErr := os.Stat(filePath)
if allowExist {
assert.True(t, allowExist && fErr == nil)
// Load the config file from JSON to map[string]interface{}
fileConfigJSON := make(map[string]interface{})
file, _ := os.Open(filePath)
defer file.Close()
decoder := json.NewDecoder(file)
err := decoder.Decode(&fileConfigJSON)
if err != nil {
t.Errorf("Error decoding config file: %s", err)
}
// Verify that the config file has the correct keys
assert.Contains(t, fileConfigJSON, "servers")
kfcServers, ok := fileConfigJSON["servers"].(map[string]interface{})
if !ok {
t.Errorf("Error decoding config file: %s", err)
assert.False(t, ok, "Error decoding config file")
return
t.Run(
fmt.Sprintf(testName), func(t *testing.T) {
_, fErr := os.Stat(filePath)
if allowExist {
if fErr != nil {
t.Errorf("Unknown error: %s", fErr)
t.FailNow()
}
assert.True(t, allowExist && fErr == nil)
// Load the config file from JSON to map[string]interface{}
fileConfigJSON := make(map[string]interface{})
file, _ := os.Open(filePath)
defer file.Close()
decoder := json.NewDecoder(file)
err := decoder.Decode(&fileConfigJSON)
if err != nil {
t.Errorf("Error decoding config file: %s", err)
}
// Verify that the config file has the correct keys
assert.Contains(t, fileConfigJSON, "servers")
kfcServers, ok := fileConfigJSON["servers"].(map[string]interface{})
if !ok {
t.Errorf("Error decoding config file: %s", err)
assert.False(t, ok, "Error decoding config file")
return
}
assert.Contains(t, kfcServers, "default")
defaultServer := kfcServers["default"].(map[string]interface{})
assert.Contains(t, defaultServer, "host")
assert.Contains(t, defaultServer, "username")
assert.Contains(t, defaultServer, "password")
} else {
assert.True(t, !allowExist && os.IsNotExist(fErr))
}
assert.Contains(t, kfcServers, "default")
defaultServer := kfcServers["default"].(map[string]interface{})
assert.Contains(t, defaultServer, "host")
assert.Contains(t, defaultServer, "kfcUsername")
assert.Contains(t, defaultServer, "kfcPassword")
} else {
assert.True(t, !allowExist && os.IsNotExist(fErr))
}
})
},
)
}

func testEnvCredsOnly(t *testing.T, filePath string, allowExist bool) {
t.Run(fmt.Sprintf("Auth w/ env ONLY"), func(t *testing.T) {
// Load .env file
err := godotenv.Load("../.env_1040")
if err != nil {
t.Errorf("Error loading .env file")
}
testLogout(t)
testConfigExists(t, filePath, false)
testConfigValid(t)
})
t.Run(
fmt.Sprintf("Auth w/ env ONLY"), func(t *testing.T) {
// Load .env file
//err := godotenv.Load("../.env_1040")
//if err != nil {
// t.Errorf("Error loading .env file")
//}
testLogout(t)
testConfigExists(t, filePath, false)
testConfigValid(t)
},
)
}

func testEnvCredsToFile(t *testing.T, filePath string, allowExist bool) {
t.Run(fmt.Sprintf("Auth w/ env ONLY"), func(t *testing.T) {
// Load .env file
err := godotenv.Load("../.env_1040")
if err != nil {
t.Errorf("Error loading .env file")
}
testLogout(t)
testConfigExists(t, filePath, false)
testConfigValid(t)
})
t.Run(
fmt.Sprintf("Auth w/ env ONLY"), func(t *testing.T) {
// Load .env file
err := godotenv.Load("../.env_1040")
if err != nil {
t.Errorf("Error loading .env file")
}
testLogout(t)
testConfigExists(t, filePath, false)
testConfigValid(t)
},
)
}

func testLoginNoPrompt(t *testing.T, filePath string) {
// Test logging in w/o args and w/o prompt
t.Run(fmt.Sprintf("login no prompt"), func(t *testing.T) {
testCmd := RootCmd
testCmd.SetArgs([]string{"login", "--no-prompt"})
noPromptErr := testCmd.Execute()
if noPromptErr != nil {
t.Errorf("RootCmd() = %v, shouldNotPass %v", noPromptErr, true)
}
testConfigExists(t, filePath, true)
os.Unsetenv("KEYFACTOR_USERNAME")
os.Unsetenv("KEYFACTOR_PASSWORD")
testConfigValid(t)
//testLogout(t)
})
t.Run(
fmt.Sprintf("login no prompt"), func(t *testing.T) {
testCmd := RootCmd
testCmd.SetArgs([]string{"login", "--no-prompt"})
noPromptErr := testCmd.Execute()
if noPromptErr != nil {
t.Errorf("RootCmd() = %v, shouldNotPass %v", noPromptErr, true)
}
testConfigExists(t, filePath, true)
os.Unsetenv("KEYFACTOR_USERNAME")
os.Unsetenv("KEYFACTOR_PASSWORD")
testConfigValid(t)
//testLogout(t)
},
)
}
54 changes: 35 additions & 19 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,14 @@ func initClient(
return authViaProvider()
}

log.Debug().Msg("call: authEnvVars()")
log.Debug().
Str("flagConfigFile", flagConfigFile).
Str("flagProfile", flagProfile).
Str("flagAuthProviderType", flagAuthProviderType).
Str("flagAuthProviderProfile", flagAuthProviderProfile).
Bool("noPrompt", noPrompt).
Bool("saveConfig", saveConfig).
Msg("call: authEnvVars()")
commandConfig, _ = authEnvVars(flagConfigFile, flagProfile, saveConfig)

// check if commandConfig is empty
Expand Down Expand Up @@ -169,24 +176,33 @@ func initClient(
}

if !validConfigFileEntry(commandConfig, flagProfile) {
if !noPrompt {
// Auth user interactively
authConfigEntry := commandConfig.Servers[flagProfile]
commandConfig, _ = authInteractive(
authConfigEntry.Hostname,
authConfigEntry.Username,
authConfigEntry.Password,
authConfigEntry.Domain,
authConfigEntry.APIPath,
flagProfile,
false,
false,
flagConfigFile,
)
} else {
//log.Fatalf("[ERROR] auth config profile: %s", flagProfile)
log.Error().Str("flagProfile", flagProfile).Msg("invalid auth config profile")
return nil, fmt.Errorf("invalid auth config profile: %s", flagProfile)
commandConfigFile, _ := authConfigFile(flagConfigFile, flagProfile, "", noPrompt, false)
// add non empty entries from commandConfigFile to commandConfig
for k, v := range commandConfigFile.Servers {
if v.Hostname != "" || v.Username != "" || v.Password != "" || v.Domain != "" || v.APIPath != "" {
commandConfig.Servers[k] = v
}
}
if !validConfigFileEntry(commandConfig, flagProfile) {
if !noPrompt {
// Auth user interactively
authConfigEntry := commandConfig.Servers[flagProfile]
commandConfig, _ = authInteractive(
authConfigEntry.Hostname,
authConfigEntry.Username,
authConfigEntry.Password,
authConfigEntry.Domain,
authConfigEntry.APIPath,
flagProfile,
false,
false,
flagConfigFile,
)
} else {
//log.Fatalf("[ERROR] auth config profile: %s", flagProfile)
log.Error().Str("flagProfile", flagProfile).Msg("invalid auth config profile")
return nil, fmt.Errorf("invalid auth config profile: %s", flagProfile)
}
}
}

Expand Down
Loading

0 comments on commit 693ae72

Please sign in to comment.