commit 108b72ed9d805d088ad5ebbe0c8eb9495ad07432
parent 6012fc032ed53ea673f849b75e989a463525f672
Author: Sean Enck <sean@ttypty.com>
Date: Sat, 5 Oct 2024 11:00:17 -0400
enable password generation fully with shell integration
Diffstat:
8 files changed, 52 insertions(+), 12 deletions(-)
diff --git a/internal/app/completions.go b/internal/app/completions.go
@@ -44,6 +44,7 @@ type (
CanClip bool
CanTOTP bool
CanList bool
+ CanGenerate bool
ReadOnly bool
IsDefault bool
Conditional string
@@ -65,6 +66,9 @@ func (p Profile) Options() []string {
if p.CanTOTP {
opts = append(opts, TOTPCommand)
}
+ if p.CanGenerate {
+ opts = append(opts, PasswordGenerateCommand)
+ }
return opts
}
@@ -95,6 +99,7 @@ func loadProfiles(exe string) []Profile {
n.CanTOTP = p.TOTP
n.ReadOnly = !p.Write
n.IsDefault = p.Default
+ n.CanGenerate = p.Generate
var sub []string
for _, e := range p.Env {
sub = append(sub, fmt.Sprintf("[ %s ]", e))
diff --git a/internal/app/completions_test.go b/internal/app/completions_test.go
@@ -51,6 +51,10 @@ func TestProfileOptions(t *testing.T) {
if len(p.Options()) != 8 {
t.Errorf("invalid options: %v", p.Options())
}
+ p.CanGenerate = true
+ if len(p.Options()) != 9 {
+ t.Errorf("invalid options: %v", p.Options())
+ }
}
func TestProfileTOTPSubOptions(t *testing.T) {
diff --git a/internal/app/core_test.go b/internal/app/core_test.go
@@ -13,7 +13,7 @@ func TestUsage(t *testing.T) {
t.Errorf("invalid usage, out of date? %d", len(u))
}
u, _ = app.Usage(true, "lb")
- if len(u) != 113 {
+ if len(u) != 114 {
t.Errorf("invalid verbose usage, out of date? %d", len(u))
}
for _, usage := range u {
diff --git a/internal/app/pwgen.go b/internal/app/pwgen.go
@@ -16,6 +16,13 @@ import (
// GeneratePassword generates a password
func GeneratePassword(cmd CommandOptions) error {
+ ok, err := config.EnvNoPasswordGen.Get()
+ if err != nil {
+ return err
+ }
+ if ok {
+ return errors.New("password generation is disabled")
+ }
length, err := config.EnvPasswordGenCount.Get()
if err != nil {
return err
diff --git a/internal/app/pwgen_test.go b/internal/app/pwgen_test.go
@@ -11,6 +11,7 @@ import (
)
func setupGenScript() string {
+ os.Clearenv()
const pwgenScript = "pwgen.sh"
pwgenPath := filepath.Join("testdata", pwgenScript)
os.WriteFile(pwgenPath, []byte(`#!/bin/sh
@@ -49,6 +50,10 @@ func TestGenerateError(t *testing.T) {
if err := app.GeneratePassword(m); err != nil {
t.Errorf("invalid error: %v", err)
}
+ os.Setenv("LOCKBOX_NOPWGEN", "yes")
+ if err := app.GeneratePassword(m); err == nil || err.Error() != "password generation is disabled" {
+ t.Errorf("invalid error: %v", err)
+ }
}
func testPasswordGen(t *testing.T, expect string) {
diff --git a/internal/config/core.go b/internal/config/core.go
@@ -93,13 +93,14 @@ type (
}
// CompletionProfile are shell completion definitions with backing environment information
CompletionProfile struct {
- Clip bool
- TOTP bool
- List bool
- Write bool
- Name string
- Env []string
- Default bool
+ Clip bool
+ TOTP bool
+ List bool
+ Write bool
+ Name string
+ Env []string
+ Default bool
+ Generate bool
}
// ReKeyArgs are the arguments for rekeying
ReKeyArgs struct {
diff --git a/internal/config/vars.go b/internal/config/vars.go
@@ -21,6 +21,7 @@ const (
roProfile = "readonly"
noTOTPProfile = "nototp"
noClipProfile = "noclip"
+ noGenProfile = "nopwgen"
// ModTimeFormat is the expected modtime format
ModTimeFormat = time.RFC3339
)
@@ -380,6 +381,15 @@ This value can NOT be an expansion itself.`,
allowed: []string{"<language code>"},
canDefault: true,
})
+ // EnvNoPasswordGen disables password generation.
+ EnvNoPasswordGen = environmentRegister(
+ EnvironmentBool{
+ environmentDefault: newDefaultedEnvironment(false,
+ environmentBase{
+ subKey: "NOPWGEN",
+ desc: "Disable password generation.",
+ }),
+ })
)
// GetReKey will get the rekey environment settings
@@ -467,6 +477,7 @@ func newProfile(keys []string) CompletionProfile {
p.List = true
p.TOTP = true
p.Write = true
+ p.Generate = true
name := ""
sort.Strings(keys)
var e []string
@@ -485,6 +496,9 @@ func newProfile(keys []string) CompletionProfile {
case roProfile:
e = append(e, exportProfileKeyValue(EnvReadOnly.environmentBase, yes))
p.Write = false
+ case noGenProfile:
+ e = append(e, exportProfileKeyValue(EnvNoPasswordGen.environmentBase, yes))
+ p.Generate = false
}
}
sort.Strings(e)
@@ -518,7 +532,7 @@ func generateProfiles(keys []string) map[string]CompletionProfile {
// LoadCompletionProfiles will generate known completion profile with backing env information
func LoadCompletionProfiles() []CompletionProfile {
- loaded := generateProfiles([]string{noClipProfile, roProfile, noTOTPProfile, askProfile})
+ loaded := generateProfiles([]string{noClipProfile, roProfile, noTOTPProfile, askProfile, noGenProfile})
var profiles []CompletionProfile
for _, v := range loaded {
profiles = append(profiles, v)
@@ -526,7 +540,7 @@ func LoadCompletionProfiles() []CompletionProfile {
sort.Slice(profiles, func(i, j int) bool {
return strings.Compare(profiles[i].Name, profiles[j].Name) < 0
})
- profiles = append(profiles, CompletionProfile{Clip: true, Write: true, TOTP: true, List: true, Default: true})
+ profiles = append(profiles, CompletionProfile{Generate: true, Clip: true, Write: true, TOTP: true, List: true, Default: true})
return profiles
}
diff --git a/internal/config/vars_test.go b/internal/config/vars_test.go
@@ -69,6 +69,10 @@ func TestIsNoClip(t *testing.T) {
checkYesNo("LOCKBOX_NOCLIP", t, config.EnvNoClip, false)
}
+func TestIsNoGeneratePassword(t *testing.T) {
+ checkYesNo("LOCKBOX_NOPWGEN", t, config.EnvNoPasswordGen, false)
+}
+
func TestIsTitle(t *testing.T) {
checkYesNo("LOCKBOX_PWGEN_TITLE", t, config.EnvPasswordGenTitle, true)
}
@@ -101,7 +105,7 @@ func TestListVariables(t *testing.T) {
known[trim] = struct{}{}
}
l := len(known)
- if l != 31 {
+ if l != 32 {
t.Errorf("invalid env count, outdated? %d", l)
}
}
@@ -267,7 +271,7 @@ func TestEnvironDefinitions(t *testing.T) {
func TestLoadCompletionProfiles(t *testing.T) {
p := config.LoadCompletionProfiles()
- if len(p) != 16 {
+ if len(p) != 32 {
t.Errorf("invalid completion count: %d", len(p))
}
exp := len(p) - 1