Merge analyze tui with trufflehog tui (#3735)
* Move pkg/analyzer/tui to pkg/tui/analyzer * [WIP] Wire up arguments into the TUI package * WIP - moving analyze inside tui Co-authored-by: mcastorina <[email protected]> * [WIP] Analyze page Co-authored-by: hxnyk <[email protected]> * Add SetSize to KeyTypePage * Use pageHistory stack for proper history traversal Co-authored-by: hxnyk <[email protected]> * Ongoing work - separate analyze pages Co-authored-by: mcastorina <[email protected]> * Set size on list Co-authored-by: mcastorina <[email protected]> * Pass analyzer as message Co-authored-by: mcastorina <[email protected]> * fix args for analyze Co-authored-by: mcastorina <[email protected]> * Actually run the analysis Co-authored-by: hxnyk <[email protected]> * Remove analyze_old Co-authored-by: hxnyk <[email protected]> * remove old comments Co-authored-by: mcastorina <[email protected]> * Remove unused analyzeKeyType --------- Co-authored-by: Miccah Castorina <[email protected]>
This commit is contained in:
@@ -269,8 +269,8 @@ func init() {
|
||||
// Support -h for help
|
||||
cli.HelpFlag.Short('h')
|
||||
|
||||
if len(os.Args) <= 1 && isatty.IsTerminal(os.Stdout.Fd()) {
|
||||
args := tui.Run()
|
||||
if isatty.IsTerminal(os.Stdout.Fd()) && (len(os.Args) <= 1 || os.Args[1] == analyzeCmd.FullCommand()) {
|
||||
args := tui.Run(os.Args[1:])
|
||||
if len(args) == 0 {
|
||||
os.Exit(0)
|
||||
}
|
||||
@@ -520,46 +520,56 @@ func run(state overseer.State) {
|
||||
return
|
||||
}
|
||||
|
||||
topLevelSubCommand, _, _ := strings.Cut(cmd, " ")
|
||||
switch topLevelSubCommand {
|
||||
case analyzeCmd.FullCommand():
|
||||
analyzer.Run(cmd)
|
||||
default:
|
||||
metrics, err := runSingleScan(ctx, cmd, engConf)
|
||||
if err != nil {
|
||||
logFatal(err, "error running scan")
|
||||
}
|
||||
|
||||
verificationCacheMetrics := struct {
|
||||
Hits int32
|
||||
Misses int32
|
||||
HitsWasted int32
|
||||
AttemptsSaved int32
|
||||
VerificationTimeSpentMS int64
|
||||
}{
|
||||
Hits: verificationCacheMetrics.ResultCacheHits.Load(),
|
||||
Misses: verificationCacheMetrics.ResultCacheMisses.Load(),
|
||||
HitsWasted: verificationCacheMetrics.ResultCacheHitsWasted.Load(),
|
||||
AttemptsSaved: verificationCacheMetrics.CredentialVerificationsSaved.Load(),
|
||||
VerificationTimeSpentMS: verificationCacheMetrics.FromDataVerifyTimeSpentMS.Load(),
|
||||
}
|
||||
|
||||
// Print results.
|
||||
logger.Info("finished scanning",
|
||||
"chunks", metrics.ChunksScanned,
|
||||
"bytes", metrics.BytesScanned,
|
||||
"verified_secrets", metrics.VerifiedSecretsFound,
|
||||
"unverified_secrets", metrics.UnverifiedSecretsFound,
|
||||
"scan_duration", metrics.ScanDuration.String(),
|
||||
"trufflehog_version", version.BuildVersion,
|
||||
"verification_caching", verificationCacheMetrics,
|
||||
)
|
||||
|
||||
if metrics.hasFoundResults && *fail {
|
||||
logger.V(2).Info("exiting with code 183 because results were found")
|
||||
os.Exit(183)
|
||||
}
|
||||
metrics, err := runSingleScan(ctx, cmd, engConf)
|
||||
if err != nil {
|
||||
logFatal(err, "error running scan")
|
||||
}
|
||||
|
||||
verificationCacheMetricsSnapshot := struct {
|
||||
Hits int32
|
||||
Misses int32
|
||||
HitsWasted int32
|
||||
AttemptsSaved int32
|
||||
VerificationTimeSpentMS int64
|
||||
}{
|
||||
Hits: verificationCacheMetrics.ResultCacheHits.Load(),
|
||||
Misses: verificationCacheMetrics.ResultCacheMisses.Load(),
|
||||
HitsWasted: verificationCacheMetrics.ResultCacheHitsWasted.Load(),
|
||||
AttemptsSaved: verificationCacheMetrics.CredentialVerificationsSaved.Load(),
|
||||
VerificationTimeSpentMS: verificationCacheMetrics.FromDataVerifyTimeSpentMS.Load(),
|
||||
}
|
||||
|
||||
// Print results.
|
||||
logger.Info("finished scanning",
|
||||
"chunks", metrics.ChunksScanned,
|
||||
"bytes", metrics.BytesScanned,
|
||||
"verified_secrets", metrics.VerifiedSecretsFound,
|
||||
"unverified_secrets", metrics.UnverifiedSecretsFound,
|
||||
"scan_duration", metrics.ScanDuration.String(),
|
||||
"trufflehog_version", version.BuildVersion,
|
||||
"verification_caching", verificationCacheMetricsSnapshot,
|
||||
)
|
||||
|
||||
if metrics.hasFoundResults && *fail {
|
||||
logger.V(2).Info("exiting with code 183 because results were found")
|
||||
os.Exit(183)
|
||||
}
|
||||
|
||||
// Print results.
|
||||
logger.Info("finished scanning",
|
||||
"chunks", metrics.ChunksScanned,
|
||||
"bytes", metrics.BytesScanned,
|
||||
"verified_secrets", metrics.VerifiedSecretsFound,
|
||||
"unverified_secrets", metrics.UnverifiedSecretsFound,
|
||||
"scan_duration", metrics.ScanDuration.String(),
|
||||
"trufflehog_version", version.BuildVersion,
|
||||
)
|
||||
|
||||
if metrics.hasFoundResults && *fail {
|
||||
logger.V(2).Info("exiting with code 183 because results were found")
|
||||
os.Exit(183)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func compareScans(ctx context.Context, cmd string, cfg engine.Config) error {
|
||||
|
||||
+8
-29
@@ -1,11 +1,9 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/alecthomas/kingpin/v2"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/airbrake"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/asana"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/bitbucket"
|
||||
@@ -28,37 +26,18 @@ import (
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/stripe"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/twilio"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/tui"
|
||||
)
|
||||
|
||||
var (
|
||||
// TODO: Add list of supported key types.
|
||||
analyzeKeyType *string
|
||||
)
|
||||
|
||||
func Command(app *kingpin.Application) *kingpin.CmdClause {
|
||||
cli := app.Command("analyze", "Analyze API keys for fine-grained permissions information.")
|
||||
|
||||
keyTypeHelp := fmt.Sprintf(
|
||||
"Type of key to analyze. Omit to interactively choose. Available key types: %s",
|
||||
strings.Join(analyzers.AvailableAnalyzers(), ", "),
|
||||
)
|
||||
// Lowercase the available analyzers.
|
||||
availableAnalyzers := make([]string, len(analyzers.AvailableAnalyzers()))
|
||||
for i, a := range analyzers.AvailableAnalyzers() {
|
||||
availableAnalyzers[i] = strings.ToLower(a)
|
||||
}
|
||||
analyzeKeyType = cli.Arg("key-type", keyTypeHelp).Enum(availableAnalyzers...)
|
||||
|
||||
return cli
|
||||
type SecretInfo struct {
|
||||
Parts map[string]string
|
||||
Cfg *config.Config
|
||||
}
|
||||
|
||||
func Run(cmd string) {
|
||||
keyType, secretInfo, err := tui.Run(*analyzeKeyType)
|
||||
if err != nil {
|
||||
// TODO: Log error.
|
||||
return
|
||||
}
|
||||
func Command(app *kingpin.Application) *kingpin.CmdClause {
|
||||
return app.Command("analyze", "Analyze API keys for fine-grained permissions information.")
|
||||
}
|
||||
|
||||
func Run(keyType string, secretInfo SecretInfo) {
|
||||
if secretInfo.Cfg == nil {
|
||||
secretInfo.Cfg = &config.Config{}
|
||||
}
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/common"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/keymap"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/styles"
|
||||
)
|
||||
|
||||
// TUI is the main TUI model.
|
||||
type TUI struct {
|
||||
keyType string
|
||||
secretInfo *SecretInfo
|
||||
common *common.Common
|
||||
model tea.Model
|
||||
}
|
||||
|
||||
type SecretInfo struct {
|
||||
Parts map[string]string
|
||||
Cfg *config.Config
|
||||
}
|
||||
|
||||
var AbortError error = errors.New("command aborted")
|
||||
|
||||
func Run(keyType string) (string, *SecretInfo, error) {
|
||||
// If a keyType is provided, make sure it's in the list of AvailableAnalyzers.
|
||||
if keyType != "" {
|
||||
var found bool
|
||||
for _, a := range analyzers.AvailableAnalyzers() {
|
||||
if strings.EqualFold(a, keyType) {
|
||||
keyType = a
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return "", nil, fmt.Errorf("Unrecognized command %q", keyType)
|
||||
}
|
||||
}
|
||||
|
||||
t := &TUI{
|
||||
keyType: keyType,
|
||||
common: &common.Common{
|
||||
KeyMap: keymap.DefaultKeyMap(),
|
||||
},
|
||||
}
|
||||
if _, err := tea.NewProgram(t).Run(); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
if t.secretInfo == nil {
|
||||
return "", nil, AbortError
|
||||
}
|
||||
return t.keyType, t.secretInfo, nil
|
||||
}
|
||||
|
||||
func (ui *TUI) Init() tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ui *TUI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
if msg, ok := msg.(tea.WindowSizeMsg); ok {
|
||||
ui.SetSize(msg)
|
||||
}
|
||||
// Always be able to force quit.
|
||||
if msg, ok := msg.(tea.KeyMsg); ok && msg.Type.String() == "ctrl+c" {
|
||||
return ui, tea.Quit
|
||||
}
|
||||
|
||||
switch m := msg.(type) {
|
||||
case SetKeyTypeMsg:
|
||||
ui.keyType = string(m)
|
||||
case SecretInfo:
|
||||
ui.secretInfo = &m
|
||||
return ui, tea.Quit
|
||||
}
|
||||
|
||||
if ui.model == nil {
|
||||
return ui, nil
|
||||
}
|
||||
|
||||
var cmd tea.Cmd
|
||||
ui.model, cmd = ui.model.Update(msg)
|
||||
return ui, cmd
|
||||
}
|
||||
|
||||
func (ui *TUI) View() string {
|
||||
if ui.model == nil {
|
||||
return "Loading..."
|
||||
}
|
||||
return ui.model.View()
|
||||
}
|
||||
|
||||
func (ui *TUI) SetSize(msg tea.WindowSizeMsg) {
|
||||
h, v := styles.AppStyle.GetFrameSize()
|
||||
h, v = msg.Width-h, msg.Height-v
|
||||
ui.common.SetSize(h, v)
|
||||
if ui.model != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Set the model only after we have size information.
|
||||
// TODO: Responsive pages.
|
||||
if ui.keyType == "" {
|
||||
ui.model = NewKeyTypePage(ui.common)
|
||||
} else {
|
||||
ui.model = NewFormPage(ui.common, ui.keyType)
|
||||
}
|
||||
}
|
||||
|
||||
type SetKeyTypeMsg string
|
||||
|
||||
func SetKeyTypeCmd(keyType string) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
return SetKeyTypeMsg(keyType)
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,38 @@
|
||||
package tui
|
||||
package analyze_form
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/bubbles/key"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/common"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/components/textinputs"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/styles"
|
||||
)
|
||||
|
||||
type FormPage struct {
|
||||
Common *common.Common
|
||||
var (
|
||||
titleStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("#FFFDF5")).
|
||||
Background(lipgloss.Color(styles.Colors["bronze"])).
|
||||
Padding(0, 1)
|
||||
)
|
||||
|
||||
type AnalyzeForm struct {
|
||||
common.Common
|
||||
KeyType string
|
||||
form textinputs.Model
|
||||
}
|
||||
|
||||
func NewFormPage(c *common.Common, keyType string) FormPage {
|
||||
type Submission struct {
|
||||
AnalyzerType string
|
||||
AnalyzerInfo analyzer.SecretInfo
|
||||
}
|
||||
|
||||
func New(c common.Common, keyType string) *AnalyzeForm {
|
||||
var inputs []textinputs.InputConfig
|
||||
switch strings.ToLower(keyType) {
|
||||
case "twilio":
|
||||
@@ -65,25 +77,31 @@ func NewFormPage(c *common.Common, keyType string) FormPage {
|
||||
SetHeader(titleStyle.Render(fmt.Sprintf("Configuring %s analyzer", keyType))).
|
||||
SetFooter("⚠️ Running TruffleHog Analyze will send a lot of requests ⚠️\n\n🚧 Please confirm you have permission to run TruffleHog Analyze against this secret 🚧").
|
||||
SetSubmitMsg("Run TruffleHog Analyze")
|
||||
return FormPage{
|
||||
return &AnalyzeForm{
|
||||
Common: c,
|
||||
KeyType: keyType,
|
||||
form: form,
|
||||
}
|
||||
}
|
||||
|
||||
func (FormPage) Init() tea.Cmd {
|
||||
func (AnalyzeForm) Init() tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ui FormPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
// TODO: Check form focus.
|
||||
if msg, ok := msg.(tea.KeyMsg); ok {
|
||||
type SetAnalyzerMsg string
|
||||
|
||||
func (ui *AnalyzeForm) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case SetAnalyzerMsg:
|
||||
ui = New(ui.Common, string(msg))
|
||||
return ui, nil
|
||||
case tea.KeyMsg:
|
||||
switch {
|
||||
case key.Matches(msg, ui.Common.KeyMap.Back):
|
||||
return ui.PrevPage()
|
||||
return nil, tea.Quit
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := msg.(textinputs.SelectNextMsg); ok {
|
||||
values := make(map[string]string)
|
||||
for k, v := range ui.form.GetInputs() {
|
||||
@@ -96,27 +114,29 @@ func (ui FormPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
LogFile: logFile,
|
||||
LoggingEnabled: logFile != "",
|
||||
}
|
||||
return SecretInfo{Cfg: &cfg, Parts: values}
|
||||
return Submission{
|
||||
AnalyzerType: ui.KeyType,
|
||||
AnalyzerInfo: analyzer.SecretInfo{Cfg: &cfg, Parts: values},
|
||||
}
|
||||
}
|
||||
return nil, secretInfoCmd
|
||||
return ui, secretInfoCmd
|
||||
}
|
||||
|
||||
form, cmd := ui.form.Update(msg)
|
||||
ui.form = form.(textinputs.Model)
|
||||
return ui, cmd
|
||||
}
|
||||
|
||||
func (ui FormPage) View() string {
|
||||
func (ui *AnalyzeForm) View() string {
|
||||
return styles.AppStyle.Render(ui.form.View())
|
||||
}
|
||||
|
||||
func (ui FormPage) PrevPage() (tea.Model, tea.Cmd) {
|
||||
page := NewKeyTypePage(ui.Common)
|
||||
// Select what was previously selected.
|
||||
index, ok := slices.BinarySearch(analyzers.AvailableAnalyzers(), ui.KeyType)
|
||||
if !ok {
|
||||
// Should be impossible.
|
||||
index = 0
|
||||
}
|
||||
page.list.Select(index)
|
||||
return page, nil
|
||||
func (m *AnalyzeForm) ShortHelp() []key.Binding {
|
||||
// TODO: actually return something
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *AnalyzeForm) FullHelp() [][]key.Binding {
|
||||
// TODO: actually return something
|
||||
return nil
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package tui
|
||||
package analyze_keys
|
||||
|
||||
import (
|
||||
"github.com/charmbracelet/bubbles/key"
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/common"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/components/selector"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/styles"
|
||||
)
|
||||
|
||||
@@ -23,17 +24,16 @@ var (
|
||||
Padding(0, 1)
|
||||
)
|
||||
|
||||
type KeyTypePage struct {
|
||||
Common *common.Common
|
||||
|
||||
type AnalyzeKeyPage struct {
|
||||
common.Common
|
||||
list list.Model
|
||||
}
|
||||
|
||||
func (ui KeyTypePage) Init() tea.Cmd {
|
||||
func (ui *AnalyzeKeyPage) Init() tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewKeyTypePage(c *common.Common) KeyTypePage {
|
||||
func New(c common.Common) *AnalyzeKeyPage {
|
||||
items := make([]list.Item, len(analyzers.AvailableAnalyzers()))
|
||||
for i, analyzerType := range analyzers.AvailableAnalyzers() {
|
||||
items[i] = KeyTypeItem(analyzerType)
|
||||
@@ -47,39 +47,43 @@ func NewKeyTypePage(c *common.Common) KeyTypePage {
|
||||
list.Title = "Select an analyzer type"
|
||||
list.SetShowStatusBar(false)
|
||||
list.Styles.Title = titleStyle
|
||||
return KeyTypePage{
|
||||
return &AnalyzeKeyPage{
|
||||
Common: c,
|
||||
list: list,
|
||||
}
|
||||
}
|
||||
|
||||
func (ui KeyTypePage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
func (ui *AnalyzeKeyPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
if !ui.list.SettingFilter() {
|
||||
switch msg := msg.(type) {
|
||||
|
||||
case tea.WindowSizeMsg:
|
||||
h, v := styles.AppStyle.GetFrameSize()
|
||||
ui.list.SetSize(msg.Width-h, msg.Height-v)
|
||||
case tea.KeyMsg:
|
||||
switch {
|
||||
case key.Matches(msg, ui.Common.KeyMap.Back):
|
||||
return nil, tea.Quit
|
||||
case key.Matches(msg, ui.Common.KeyMap.Select):
|
||||
chosen := string(ui.list.SelectedItem().(KeyTypeItem))
|
||||
return NewFormPage(ui.Common, chosen), SetKeyTypeCmd(chosen)
|
||||
chosenAnalyzer := ui.list.SelectedItem().(KeyTypeItem)
|
||||
|
||||
return ui, func() tea.Msg {
|
||||
return selector.SelectMsg{IdentifiableItem: chosenAnalyzer}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var cmd tea.Cmd
|
||||
ui.list, cmd = ui.list.Update(msg)
|
||||
return ui, cmd
|
||||
}
|
||||
|
||||
var cmd tea.Cmd
|
||||
ui.list, cmd = ui.list.Update(msg)
|
||||
return ui, cmd
|
||||
return ui, func() tea.Msg { return nil }
|
||||
}
|
||||
|
||||
func (ui KeyTypePage) View() string {
|
||||
func (ui AnalyzeKeyPage) View() string {
|
||||
return styles.AppStyle.Render(ui.list.View())
|
||||
}
|
||||
|
||||
func (ui KeyTypePage) NextPage(keyType string) (tea.Model, tea.Cmd) {
|
||||
return NewFormPage(ui.Common, keyType), SetKeyTypeCmd(keyType)
|
||||
}
|
||||
|
||||
type KeyTypeItem string
|
||||
|
||||
func (i KeyTypeItem) ID() string { return string(i) }
|
||||
@@ -87,10 +91,12 @@ func (i KeyTypeItem) Title() string { return string(i) }
|
||||
func (i KeyTypeItem) Description() string { return "" }
|
||||
func (i KeyTypeItem) FilterValue() string { return string(i) }
|
||||
|
||||
func init() {
|
||||
// Preload HasDarkBackground call. For some reason, if we don't do
|
||||
// this, the TUI can take a noticeably long time to start. We should
|
||||
// investigate further, but this is a good-enough bandaid for now.
|
||||
// See: https://github.com/charmbracelet/lipgloss/issues/73
|
||||
_ = lipgloss.HasDarkBackground()
|
||||
func (m AnalyzeKeyPage) ShortHelp() []key.Binding {
|
||||
// TODO: actually return something
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m AnalyzeKeyPage) FullHelp() [][]key.Binding {
|
||||
// TODO: actually return something
|
||||
return nil
|
||||
}
|
||||
+93
-25
@@ -8,9 +8,13 @@ import (
|
||||
"github.com/charmbracelet/bubbles/key"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
zone "github.com/lrstanley/bubblezone"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/common"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/components/selector"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/keymap"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/pages/analyze_form"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/pages/analyze_keys"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/pages/contact_enterprise"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/pages/source_configure"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/pages/source_select"
|
||||
@@ -27,6 +31,8 @@ const (
|
||||
sourceConfigurePage
|
||||
viewOSSProjectPage
|
||||
contactEnterprisePage
|
||||
analyzeKeysPage
|
||||
analyzeFormPage
|
||||
)
|
||||
|
||||
type sessionState int
|
||||
@@ -39,20 +45,31 @@ const (
|
||||
|
||||
// TUI is the main TUI model.
|
||||
type TUI struct {
|
||||
common common.Common
|
||||
pages []common.Component
|
||||
activePage page
|
||||
state sessionState
|
||||
args []string
|
||||
common common.Common
|
||||
pages []common.Component
|
||||
pageHistory []page
|
||||
state sessionState
|
||||
args []string
|
||||
|
||||
// Analyzer specific values that are only set if running an analysis.
|
||||
analyzerType string
|
||||
analyzerInfo analyzer.SecretInfo
|
||||
}
|
||||
|
||||
// New returns a new TUI model.
|
||||
func New(c common.Common) *TUI {
|
||||
func New(c common.Common, args []string) *TUI {
|
||||
ui := &TUI{
|
||||
common: c,
|
||||
pages: make([]common.Component, 5),
|
||||
activePage: wizardIntroPage,
|
||||
state: startState,
|
||||
common: c,
|
||||
pages: make([]common.Component, 7),
|
||||
pageHistory: []page{wizardIntroPage},
|
||||
state: startState,
|
||||
args: args,
|
||||
}
|
||||
switch {
|
||||
case len(args) == 0:
|
||||
return ui
|
||||
case len(args) == 1 && args[0] == "analyze":
|
||||
ui.pageHistory = []page{wizardIntroPage, analyzeKeysPage}
|
||||
}
|
||||
return ui
|
||||
}
|
||||
@@ -69,11 +86,28 @@ func (ui *TUI) SetSize(width, height int) {
|
||||
|
||||
// Init implements tea.Model.
|
||||
func (ui *TUI) Init() tea.Cmd {
|
||||
|
||||
ui.pages[wizardIntroPage] = wizard_intro.New(ui.common)
|
||||
ui.pages[sourceSelectPage] = source_select.New(ui.common)
|
||||
ui.pages[sourceConfigurePage] = source_configure.New(ui.common)
|
||||
ui.pages[viewOSSProjectPage] = view_oss.New(ui.common)
|
||||
ui.pages[contactEnterprisePage] = contact_enterprise.New(ui.common)
|
||||
ui.pages[analyzeKeysPage] = analyze_keys.New(ui.common)
|
||||
|
||||
if len(ui.args) > 1 && ui.args[0] == "analyze" {
|
||||
analyzerArg := strings.ToLower(ui.args[1])
|
||||
ui.pages[analyzeFormPage] = analyze_form.New(ui.common, analyzerArg)
|
||||
ui.setActivePage(analyzeKeysPage)
|
||||
|
||||
for _, analyzer := range analyzers.AvailableAnalyzers() {
|
||||
if strings.ToLower(analyzer) == analyzerArg {
|
||||
ui.setActivePage(analyzeFormPage)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ui.pages[analyzeFormPage] = analyze_form.New(ui.common, "this is a bug")
|
||||
}
|
||||
|
||||
ui.SetSize(ui.common.Width, ui.common.Height)
|
||||
cmds := make([]tea.Cmd, 0)
|
||||
cmds = append(cmds,
|
||||
@@ -82,6 +116,8 @@ func (ui *TUI) Init() tea.Cmd {
|
||||
ui.pages[sourceConfigurePage].Init(),
|
||||
ui.pages[viewOSSProjectPage].Init(),
|
||||
ui.pages[contactEnterprisePage].Init(),
|
||||
ui.pages[analyzeKeysPage].Init(),
|
||||
ui.pages[analyzeFormPage].Init(),
|
||||
)
|
||||
ui.state = loadedState
|
||||
ui.SetSize(ui.common.Width, ui.common.Height)
|
||||
@@ -106,12 +142,12 @@ func (ui *TUI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
case tea.KeyMsg:
|
||||
switch {
|
||||
case key.Matches(msg, ui.common.KeyMap.Help):
|
||||
case key.Matches(msg, ui.common.KeyMap.CmdQuit) && ui.activePage != sourceConfigurePage:
|
||||
case key.Matches(msg, ui.common.KeyMap.CmdQuit) && ui.activePage() != sourceConfigurePage:
|
||||
return ui, tea.Quit
|
||||
case key.Matches(msg, ui.common.KeyMap.Quit):
|
||||
return ui, tea.Quit
|
||||
case ui.activePage > 0 && key.Matches(msg, ui.common.KeyMap.Back):
|
||||
ui.activePage -= 1
|
||||
case ui.activePage() > 0 && key.Matches(msg, ui.common.KeyMap.Back):
|
||||
_ = ui.popHistory()
|
||||
return ui, nil
|
||||
}
|
||||
}
|
||||
@@ -120,37 +156,45 @@ func (ui *TUI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
case selector.SelectMsg:
|
||||
switch item := msg.IdentifiableItem.(type) {
|
||||
case wizard_intro.Item:
|
||||
|
||||
switch item {
|
||||
case wizard_intro.Quit:
|
||||
cmds = append(cmds, tea.Quit)
|
||||
case wizard_intro.ViewOSSProject:
|
||||
ui.activePage = viewOSSProjectPage
|
||||
ui.setActivePage(viewOSSProjectPage)
|
||||
case wizard_intro.ViewHelpDocs:
|
||||
ui.args = []string{"--help"}
|
||||
|
||||
return ui, tea.Quit
|
||||
case wizard_intro.EnterpriseInquire:
|
||||
ui.activePage = contactEnterprisePage
|
||||
ui.setActivePage(contactEnterprisePage)
|
||||
case wizard_intro.ScanSourceWithWizard:
|
||||
ui.activePage = sourceSelectPage
|
||||
ui.setActivePage(sourceSelectPage)
|
||||
case wizard_intro.AnalyzeSecret:
|
||||
ui.args = []string{"analyze"}
|
||||
return ui, tea.Quit
|
||||
ui.setActivePage(analyzeKeysPage)
|
||||
}
|
||||
case source_select.SourceItem:
|
||||
ui.activePage = sourceConfigurePage
|
||||
ui.setActivePage(sourceConfigurePage)
|
||||
cmds = append(cmds, func() tea.Msg {
|
||||
return source_configure.SetSourceMsg{Source: item.ID()}
|
||||
})
|
||||
case analyze_keys.KeyTypeItem:
|
||||
ui.setActivePage(analyzeFormPage)
|
||||
cmds = append(cmds, func() tea.Msg {
|
||||
return analyze_form.SetAnalyzerMsg(item.ID())
|
||||
})
|
||||
}
|
||||
case source_configure.SetArgsMsg:
|
||||
ui.args = strings.Split(string(msg), " ")[1:]
|
||||
return ui, tea.Quit
|
||||
case analyze_form.Submission:
|
||||
ui.analyzerType = msg.AnalyzerType
|
||||
ui.analyzerInfo = msg.AnalyzerInfo
|
||||
return ui, tea.Quit
|
||||
}
|
||||
|
||||
if ui.state == loadedState {
|
||||
m, cmd := ui.pages[ui.activePage].Update(msg)
|
||||
ui.pages[ui.activePage] = m.(common.Component)
|
||||
m, cmd := ui.pages[ui.activePage()].Update(msg)
|
||||
ui.pages[ui.activePage()] = m.(common.Component)
|
||||
if cmd != nil {
|
||||
cmds = append(cmds, cmd)
|
||||
}
|
||||
@@ -168,7 +212,7 @@ func (ui *TUI) View() string {
|
||||
case startState:
|
||||
view = "Loading..."
|
||||
case loadedState:
|
||||
view = ui.pages[ui.activePage].View()
|
||||
view = ui.pages[ui.activePage()].View()
|
||||
default:
|
||||
view = "Unknown state :/ this is a bug!"
|
||||
}
|
||||
@@ -177,7 +221,7 @@ func (ui *TUI) View() string {
|
||||
)
|
||||
}
|
||||
|
||||
func Run() []string {
|
||||
func Run(args []string) []string {
|
||||
c := common.Common{
|
||||
Copy: nil,
|
||||
Styles: styles.DefaultStyles(),
|
||||
@@ -186,12 +230,36 @@ func Run() []string {
|
||||
Height: 0,
|
||||
Zone: zone.New(),
|
||||
}
|
||||
m := New(c)
|
||||
m := New(c, args)
|
||||
p := tea.NewProgram(m)
|
||||
// TODO: Print normal help message.
|
||||
if _, err := p.Run(); err != nil {
|
||||
fmt.Printf("Alas, there's been an error: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if m.analyzerType != "" {
|
||||
analyzer.Run(m.analyzerType, m.analyzerInfo)
|
||||
os.Exit(0)
|
||||
}
|
||||
return m.args
|
||||
}
|
||||
|
||||
func (ui *TUI) activePage() page {
|
||||
if len(ui.pageHistory) == 0 {
|
||||
return wizardIntroPage
|
||||
}
|
||||
return ui.pageHistory[len(ui.pageHistory)-1]
|
||||
}
|
||||
|
||||
func (ui *TUI) popHistory() page {
|
||||
if len(ui.pageHistory) == 0 {
|
||||
return wizardIntroPage
|
||||
}
|
||||
p := ui.activePage()
|
||||
ui.pageHistory = ui.pageHistory[:len(ui.pageHistory)-1]
|
||||
return p
|
||||
}
|
||||
|
||||
func (ui *TUI) setActivePage(p page) {
|
||||
ui.pageHistory = append(ui.pageHistory, p)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user