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:
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")
}