Files
sailpoint-cli/cmd/root/configure.go

232 lines
5.6 KiB
Go

// Copyright (c) 2021, SailPoint Technologies, Inc. All rights reserved.
package root
import (
"bufio"
"errors"
"fmt"
"log"
"path/filepath"
"strconv"
"strings"
"github.com/charmbracelet/bubbles/list"
tea "github.com/charmbracelet/bubbletea"
"github.com/sailpoint-oss/sailpoint-cli/internal/auth"
"github.com/sailpoint-oss/sailpoint-cli/internal/client"
tuilist "github.com/sailpoint-oss/sailpoint-cli/internal/tui/list"
"github.com/sailpoint-oss/sailpoint-cli/internal/types"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"os"
)
const (
baseURLTemplate = "https://%s.api.identitynow.com"
tokenURLTemplate = "https://%s.api.identitynow.com/oauth/token"
authUrlTemplate = "https://%s.identitynow.com/oauth/authorize"
configFolder = ".sailpoint"
configYamlFile = "config.yaml"
)
func PromptAuth() (string, error) {
items := []list.Item{
tuilist.Item("PAT"),
tuilist.Item("OAuth"),
}
const defaultWidth = 20
l := list.New(items, tuilist.ItemDelegate{}, defaultWidth, tuilist.ListHeight)
l.Title = "What authentication method do you want to use?"
l.SetShowStatusBar(false)
l.SetFilteringEnabled(false)
l.Styles.Title = tuilist.TitleStyle
l.Styles.PaginationStyle = tuilist.PaginationStyle
l.Styles.HelpStyle = tuilist.HelpStyle
m := tuilist.Model{List: l}
_, err := tea.NewProgram(m).Run()
if err != nil {
return "", err
}
choice := m.Retrieve()
return choice, nil
}
func newConfigureCmd(client client.Client) *cobra.Command {
var debug bool
cmd := &cobra.Command{
Use: "configure",
Short: "Configure Authentication for the CLI",
Long: "\nConfigure Authentication for the CLI\nSupported Methods: (PAT, OAuth)\n\nPrerequisites:\n\nPAT:\n Tenant\n Client ID\n Client Secret\n\nOAuth:\n Tenant\n Client ID\n Client Secret - Optional Depending on configuration\n Callback Port (ex. http://localhost:{3000}/callback)\n Callback Path (ex. http://localhost:3000{/callback})",
Aliases: []string{"conf"},
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
var AuthType string
var err error
if len(args) > 0 {
AuthType = args[0]
} else {
AuthType, err = PromptAuth()
if err != nil {
return err
}
}
config, err := getConfigureParamsFromStdin(AuthType, debug)
if err != nil {
return err
}
err = updateConfigFile(config)
if err != nil {
return err
}
switch strings.ToLower(AuthType) {
case "pat":
_, err = auth.PATLogin(config, cmd.Context())
if err != nil {
return err
}
case "oauth":
_, err := auth.OAuthLogin(config)
if err != nil {
return err
}
default:
return errors.New("invalid authtype")
}
return nil
},
}
cmd.Flags().BoolVarP(&debug, "Debug", "d", false, "Specifies if the debug flag should be set")
return cmd
}
func updateConfigFile(conf types.OrgConfig) error {
home, err := os.UserHomeDir()
if err != nil {
return err
}
if _, err := os.Stat(filepath.Join(home, configFolder)); os.IsNotExist(err) {
err = os.Mkdir(filepath.Join(home, configFolder), 0777)
if err != nil {
log.Printf("failed to create %s folder for config. %v", configFolder, err)
}
}
viper.Set("authtype", conf.AuthType)
viper.Set("debug", conf.Debug)
switch strings.ToLower(conf.AuthType) {
case "pat":
viper.Set("pat", conf.Pat)
case "oauth":
viper.Set("oauth", conf.OAuth)
}
err = viper.WriteConfig()
if err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
err = viper.SafeWriteConfig()
if err != nil {
return err
}
} else {
return err
}
}
return nil
}
func getConfigureParamsFromStdin(AuthType string, debug bool) (types.OrgConfig, error) {
var conf types.OrgConfig
switch strings.ToLower(AuthType) {
case "pat":
paramsNames := []string{
"Tenant (ex. {tenant}.identitynow.com): ",
"Personal Access Token Client ID: ",
"Personal Access Token Client Secret: ",
}
scanner := bufio.NewScanner(os.Stdin)
for _, pm := range paramsNames {
fmt.Print(pm)
scanner.Scan()
value := scanner.Text()
if value == "" {
return conf, fmt.Errorf("%s parameter is empty", pm[:len(pm)-2])
}
switch pm {
case paramsNames[0]:
conf.Pat.Tenant = value
conf.Pat.BaseUrl = fmt.Sprintf(baseURLTemplate, value)
conf.Pat.TokenUrl = fmt.Sprintf(tokenURLTemplate, value)
case paramsNames[1]:
conf.Pat.ClientID = value
case paramsNames[2]:
conf.Pat.ClientSecret = value
}
}
conf.AuthType = AuthType
return conf, nil
case "oauth":
paramsNames := []string{
"Tenant (ex. {tenant}.identitynow.com): ",
"OAuth Client ID: ",
"OAuth Client Secret: ",
"OAuth Redirect Port (ex. http://localhost:{3000}/callback): ",
"OAuth Redirect Path (ex. http://localhost:3000{/callback}): ",
}
scanner := bufio.NewScanner(os.Stdin)
var OAuth types.OAuthConfig
for _, pm := range paramsNames {
fmt.Print(pm)
scanner.Scan()
value := scanner.Text()
if value == "" && pm != paramsNames[2] {
return conf, fmt.Errorf("%s parameter is empty", pm[:len(pm)-2])
}
switch pm {
case paramsNames[0]:
OAuth.Tenant = value
OAuth.BaseUrl = fmt.Sprintf(baseURLTemplate, value)
OAuth.TokenUrl = fmt.Sprintf(tokenURLTemplate, value)
OAuth.AuthUrl = fmt.Sprintf(authUrlTemplate, value)
case paramsNames[1]:
OAuth.ClientID = value
case paramsNames[2]:
OAuth.ClientSecret = value
case paramsNames[3]:
OAuth.Redirect.Port, _ = strconv.Atoi(value)
case paramsNames[4]:
OAuth.Redirect.Path = value
}
}
conf.OAuth = OAuth
conf.AuthType = AuthType
return conf, nil
}
return conf, errors.New("invalid auth type provided")
}