Skip to content

Commit

Permalink
Automatically parse Lastpass note values
Browse files Browse the repository at this point in the history
  • Loading branch information
twpayne committed Dec 18, 2018
1 parent f01eae1 commit d068f60
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 0 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,14 @@ the field you want. For example, to extract the `password` field from first the

githubPassword = {{ (index (lastpass "Github") 0).password }}

`chezmoi` automatically parses the `note` value of the Lastpass entry, so, for
example, you can extract a private SSH key like this:

{{ (index (lastpass "SSH") 0).note.privateKey }}

Keys in the `note` section written as `CamelCase Words` are converted to
`camelCaseWords`.

### Using keyring

`chezmoi` includes support for Keychain (on macOS), GNOME Keyring (on Linux),
Expand Down
33 changes: 33 additions & 0 deletions cmd/lastpass.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package cmd

import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"os/exec"
"regexp"
"strings"
"unicode"

"github.com/spf13/cobra"
"github.com/twpayne/chezmoi/lib/chezmoi"
Expand All @@ -17,6 +21,8 @@ var lastpassCommand = &cobra.Command{
RunE: makeRunE(config.runLastPassCommand),
}

var lastpassParseNoteRegexp = regexp.MustCompile(`\A([ A-Za-z]*):(.*)\z`)

// A LastPassCommandConfig is a configuration for the lastpass command.
type LastPassCommandConfig struct {
Lpass string
Expand Down Expand Up @@ -51,6 +57,33 @@ func (c *Config) lastpassFunc(id string) interface{} {
if err := json.Unmarshal(output, &data); err != nil {
chezmoi.ReturnTemplateFuncError(fmt.Errorf("lastpass %q: %s show -j %q: %v", id, name, id, err))
}
for _, d := range data {
if note, ok := d["note"].(string); ok {
d["note"] = lastpassParseNote(note)
}
}
lastPassCache[id] = data
return data
}

func lastpassParseNote(note string) map[string]string {
result := make(map[string]string)
s := bufio.NewScanner(bytes.NewBufferString(note))
key := ""
for s.Scan() {
if m := lastpassParseNoteRegexp.FindStringSubmatch(s.Text()); m != nil {
keyComponents := strings.Split(m[1], " ")
firstComponentRunes := []rune(keyComponents[0])
firstComponentRunes[0] = unicode.ToLower(firstComponentRunes[0])
keyComponents[0] = string(firstComponentRunes)
key = strings.Join(keyComponents, "")
result[key] = m[2] + "\n"
} else {
result[key] += s.Text() + "\n"
}
}
if err := s.Err(); err != nil {
chezmoi.ReturnTemplateFuncError(fmt.Errorf("lastpassParseNote: %v", err))
}
return result
}
47 changes: 47 additions & 0 deletions cmd/lastpass_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package cmd

import (
"testing"

"github.com/d4l3k/messagediff"
)

func Test_lastpassParseNote(t *testing.T) {
for _, tc := range []struct {
note string
want map[string]string
}{
{
note: "Foo:bar\n",
want: map[string]string{
"foo": "bar\n",
},
},
{
note: "Foo:bar\nbaz\n",
want: map[string]string{
"foo": "bar\nbaz\n",
},
},
{
note: "NoteType:SSH Key\nLanguage:en-US\nBit Strength:2048\nFormat:RSA\nPassphrase:Passphrase\nPrivate Key:-----BEGIN OPENSSH PRIVATE KEY-----\n-----END OPENSSH PRIVATE KEY-----\nPublic Key:ssh-rsa public-key user@host\nHostname:Hostname\nDate:Date\nNotes:",
want: map[string]string{
"noteType": "SSH Key\n",
"language": "en-US\n",
"bitStrength": "2048\n",
"format": "RSA\n",
"passphrase": "Passphrase\n",
"privateKey": "-----BEGIN OPENSSH PRIVATE KEY-----\n-----END OPENSSH PRIVATE KEY-----\n",
"publicKey": "ssh-rsa public-key user@host\n",
"hostname": "Hostname\n",
"date": "Date\n",
"notes": "\n",
},
},
} {
got := lastpassParseNote(tc.note)
if diff, equal := messagediff.PrettyDiff(tc.want, got); !equal {
t.Errorf("lastpassParseNote(%q) == %+v, want %+v\n%s", tc.note, got, tc.want, diff)
}
}
}

0 comments on commit d068f60

Please sign in to comment.