lockbox

password manager
Log | Files | Refs | README | LICENSE

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:
Minternal/app/completions.go | 5+++++
Minternal/app/completions_test.go | 4++++
Minternal/app/core_test.go | 2+-
Minternal/app/pwgen.go | 7+++++++
Minternal/app/pwgen_test.go | 5+++++
Minternal/config/core.go | 15++++++++-------
Minternal/config/vars.go | 18++++++++++++++++--
Minternal/config/vars_test.go | 8++++++--
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