lockbox

password manager
Log | Files | Refs | README | LICENSE

commit 98ee47e09c374e03b4ecd71e37398ed25fc607e7
parent 1547e5381fbadadbc04aafe6ca687065b5a75b06
Author: Sean Enck <sean@ttypty.com>
Date:   Sat, 15 Oct 2022 15:46:03 -0400

allow disabling of TOTP via interface

Diffstat:
Mcmd/main.go | 7+++++++
Minternal/cli/completions.bash | 8++++++--
Minternal/cli/core.go | 10++++++++++
Minternal/cli/core_test.go | 17++++++++++++-----
Minternal/inputs/env.go | 7+++++++
Minternal/inputs/env_test.go | 6+++++-
Minternal/totp/core.go | 12++++++++++++
7 files changed, 59 insertions(+), 8 deletions(-)

diff --git a/cmd/main.go b/cmd/main.go @@ -173,6 +173,13 @@ func run() error { case cli.InsertMultiCommand: multi = true case cli.InsertTOTPCommand: + off, err := inputs.IsNoTOTP() + if err != nil { + return err + } + if off { + return totp.ErrNoTOTP + } isTOTP = true default: return errors.New("unknown argument") diff --git a/internal/cli/completions.bash b/internal/cli/completions.bash @@ -13,18 +13,20 @@ _{{ $.Executable }}() { case ${COMP_WORDS[1]} in {{ if not $.ReadOnly }} "{{ $.InsertCommand }}") - opts="{{ $.InsertMultiCommand }} {{ $.InsertTOTPCommand }} $({{ $.DoList }})" + opts="{{ $.InsertMultiCommand }}{{ if $.CanTOTP }} {{ $.InsertTOTPCommand }}{{end}} $({{ $.DoList }})" ;; "{{ $.MoveCommand }}") opts=$({{ $.DoList }}) ;; {{end}} +{{ if $.CanTOTP }} "{{ $.TOTPCommand }}") opts="{{ $.TOTPShortCommand }} {{ $.TOTPOnceCommand }} "$({{ $.DoTOTPList }}) {{ if $.CanClip }} opts="$opts {{ $.TOTPClipCommand }}" {{end}} ;; +{{end}} "{{ $.ShowCommand }}" {{ if not $.ReadOnly }}| "{{ $.RemoveCommand }}" {{end}} {{ if $.CanClip }} | "{{ $.ClipCommand }}" {{end}}) opts=$({{ $.DoList }}) ;; @@ -34,7 +36,7 @@ _{{ $.Executable }}() { case "${COMP_WORDS[1]}" in {{ if not $.ReadOnly }} "{{ $.InsertCommand }}") - if [ "${COMP_WORDS[2]}" == "{{ $.InsertMultiCommand }}" ] || [ "${COMP_WORDS[2]}" == "{{ $.InsertTOTPCommand }}" ]; then + if [ "${COMP_WORDS[2]}" == "{{ $.InsertMultiCommand }}" ] {{ if $.CanTOTP }}|| [ "${COMP_WORDS[2]}" == "{{ $.InsertTOTPCommand }}" ] {{end}}; then opts=$({{ $.DoList }}) fi ;; @@ -42,6 +44,7 @@ _{{ $.Executable }}() { opts=$({{ $.DoList }}) ;; {{end}} +{{ if $.CanTOTP }} "{{ $.TOTPCommand }}") needs=0 if [ "${COMP_WORDS[2]}" == "{{ $.TOTPOnceCommand }}" ] || [ "${COMP_WORDS[2]}" == "{{ $.TOTPShortCommand }}" ]; then @@ -57,6 +60,7 @@ _{{ $.Executable }}() { opts=$({{ $.DoTOTPList }}) fi ;; +{{end}} esac fi if [ -n "$opts" ]; then diff --git a/internal/cli/core.go b/internal/cli/core.go @@ -70,6 +70,7 @@ type ( Completions struct { Options []string CanClip bool + CanTOTP bool ReadOnly bool InsertCommand string TOTPShortCommand string @@ -136,6 +137,7 @@ func BashCompletions(defaults bool) ([]string, error) { } isReadOnly := false isClip := true + isTOTP := true if !defaults { ro, err := inputs.IsReadOnly() if err != nil { @@ -149,9 +151,17 @@ func BashCompletions(defaults bool) ([]string, error) { if noClip { isClip = false } + noTOTP, err := inputs.IsNoTOTP() + if err != nil { + return nil, err + } + if noTOTP { + isTOTP = false + } } c.CanClip = isClip c.ReadOnly = isReadOnly + c.CanTOTP = isTOTP options := []string{EnvCommand, FindCommand, HelpCommand, ListCommand, ShowCommand, TOTPCommand, VersionCommand} if c.CanClip { options = append(options, ClipCommand) diff --git a/internal/cli/core_test.go b/internal/cli/core_test.go @@ -15,27 +15,34 @@ func TestUsage(t *testing.T) { } func TestCompletionsBash(t *testing.T) { + os.Setenv("LOCKBOX_NOTOTP", "yes") os.Setenv("LOCKBOX_READONLY", "yes") os.Setenv("LOCKBOX_NOCLIP", "yes") defaults, _ := cli.BashCompletions(true) + roNoTOTPClip, _ := cli.BashCompletions(false) + if roNoTOTPClip[0] == defaults[0] { + t.Error("should not match defaults") + } + os.Setenv("LOCKBOX_NOTOTP", "") roNoClip, _ := cli.BashCompletions(false) - if roNoClip[0] == defaults[0] { - t.Error("should not equal defaults") + if roNoClip[0] == defaults[0] || roNoClip[0] == roNoTOTPClip[0] { + t.Error("should not equal defaults nor no totp/clip") } os.Setenv("LOCKBOX_READONLY", "") os.Setenv("LOCKBOX_NOCLIP", "yes") noClip, _ := cli.BashCompletions(false) - if roNoClip[0] == noClip[0] || noClip[0] == defaults[0] { - t.Error("readonly/noclip != noclip (nor defaults)") + if roNoClip[0] == noClip[0] || noClip[0] == defaults[0] || noClip[0] == roNoTOTPClip[0] { + t.Error("readonly/noclip != noclip (nor defaults, nor ro/no totp/clip)") } os.Setenv("LOCKBOX_READONLY", "yes") os.Setenv("LOCKBOX_NOCLIP", "") ro, _ := cli.BashCompletions(false) - if roNoClip[0] == ro[0] || noClip[0] == ro[0] || ro[0] == defaults[0] { + if roNoClip[0] == ro[0] || noClip[0] == ro[0] || ro[0] == defaults[0] || ro[0] == roNoTOTPClip[0] { t.Error("readonly/noclip != ro (nor ro == noclip, nor ro == defaults)") } os.Setenv("LOCKBOX_READONLY", "") os.Setenv("LOCKBOX_NOCLIP", "") + os.Setenv("LOCKBOX_NOTOTP", "") isDefaultsToo, _ := cli.BashCompletions(false) if isDefaultsToo[0] != defaults[0] { t.Error("defaults should match env defaults") diff --git a/internal/inputs/env.go b/internal/inputs/env.go @@ -55,6 +55,7 @@ const ( colorWindowDelimiter = "," colorWindowSpan = ":" detectedValue = "(detected)" + noTOTPEnv = prefixKey + "NOTOTP" ) var ( @@ -197,6 +198,11 @@ func isYesNoEnv(defaultValue bool, env string) (bool, error) { return false, fmt.Errorf("invalid yes/no env value for %s", env) } +// IsNoTOTP indicates if TOTP is disabled +func IsNoTOTP() (bool, error) { + return isYesNoEnv(false, noTOTPEnv) +} + // IsReadOnly indicates to operate in readonly, no writing to file allowed func IsReadOnly() (bool, error) { return isYesNoEnv(false, readOnlyEnv) @@ -275,5 +281,6 @@ func ListEnvironmentVariables(showValues bool) []string { results = append(results, e.formatEnvironmentVariable(false, ClipPasteEnv, detectedValue, "override the detected platform copy command", []string{commandArgsExample})) results = append(results, e.formatEnvironmentVariable(false, clipMaxEnv, fmt.Sprintf("%d", defaultMaxClipboard), "override the amount of time before totp clears the clipboard (e.g. 10), must be an integer", []string{"integer"})) results = append(results, e.formatEnvironmentVariable(false, PlatformEnv, detectedValue, "override the detected platform", []string{MacOSPlatform, LinuxWaylandPlatform, LinuxXPlatform, WindowsLinuxPlatform})) + results = append(results, e.formatEnvironmentVariable(false, noTOTPEnv, isNo, "disable TOTP integrations", isYesNoArgs)) return results } diff --git a/internal/inputs/env_test.go b/internal/inputs/env_test.go @@ -106,6 +106,10 @@ func TestIsReadOnly(t *testing.T) { checkYesNo("LOCKBOX_READONLY", t, inputs.IsReadOnly, false) } +func TestIsNoTOTP(t *testing.T) { + checkYesNo("LOCKBOX_NOTOTP", t, inputs.IsNoTOTP, false) +} + func TestIsNoClip(t *testing.T) { checkYesNo("LOCKBOX_NOCLIP", t, inputs.IsNoClipEnabled, false) } @@ -146,7 +150,7 @@ func TestGetKey(t *testing.T) { func TestListVariables(t *testing.T) { vars := inputs.ListEnvironmentVariables(false) - if len(vars) != 14 { + if len(vars) != 15 { t.Errorf("invalid env count, outdated? %d", len(vars)) } } diff --git a/internal/totp/core.go b/internal/totp/core.go @@ -18,6 +18,11 @@ import ( otp "github.com/pquerna/otp/totp" ) +var ( + // ErrNoTOTP is used when TOTP is requested BUT is disabled + ErrNoTOTP = errors.New("TOTP is disabled") +) + type ( arguments struct { Clip bool @@ -171,6 +176,13 @@ func display(token string, args arguments) error { // Call handles UI for TOTP tokens. func Call(args []string) error { + off, err := inputs.IsNoTOTP() + if err != nil { + return err + } + if off { + return ErrNoTOTP + } if len(args) > 2 || len(args) < 1 { return errors.New("invalid arguments, subkey and entry required") }