Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# `protonPassAttachment` *share-id* *item-id* *attachment-id*

`protonPassAttachment` returns the content of the given attachment from [Proton
Pass][protonpass] using the [Proton Pass CLI][protonpass-cli]. The output of
`pass-cli` is cached so calling `protonPassAttachment` multiple times with the
same *share-id*, *item-id*, and *attachment-id* will only invoke `pass-cli`
once.

!!! example

```
{{ protonPassAttachment "$SHARE_ID" "$ITEM_ID" "$ATTACHMENT_ID" }}
```

[protonpass]: https://proton.me/pass
[protonpass-cli]: https://protonpass.github.io/pass-cli/
19 changes: 15 additions & 4 deletions assets/chezmoi.io/docs/user-guide/password-managers/proton-pass.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,31 @@ Log in to Proton Pass using
pass-cli login
```

The output of `pass-cli item view pass://$SHARE_ID/$ITEM_ID/$FIELD` is available as the
`protonPass` template function, for example:
The output of `pass-cli item view pass://$SHARE_ID/$ITEM_ID/$FIELD` is
available with the [`protonPass`][protonpasstemplatefunc] template function, for
example:

```text
{{ protonPass "pass://$SHARE_ID/$ITEM_ID/$FIELD" }}
```

The output of `pass-cli item view --output=json pass://$SHARE_ID/$ITEM_ID` is
available as `protonPassJSON` and returns the structured data the item holds.
For example:
available with [`protonPassJSON`][protonpassjson] which returns the structured
data the item holds. For example:

```text
{{ (protonPassJSON "pass://$SHARE_ID/$ITEM_ID").item.content.content.key.password }}
```

The contents of attachments are available using the
[`protonPassAttachment`][protonpassattachment] template function, for example:

```
{{ protonPassAttachment "$SHARE_ID" "$ITEM_ID" "$ATTACHMENT_ID" }}
```

[protonpass]: https://proton.me/pass
[protonpassattachment]: ../../reference/templates/protonpass-functions/protonPassAttachment.md
[protonpassjson]: ../../reference/templates/protonpass-functions/protonPassJSON.md
[protonpasstemplatefunc]: ../../reference/templates/protonpass-functions/protonPass.md
[cli]: https://protonpass.github.io/pass-cli
1 change: 1 addition & 0 deletions assets/chezmoi.io/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,7 @@ nav:
- Proton Pass functions:
- reference/templates/protonpass-functions/index.md
- protonPass: reference/templates/protonpass-functions/protonPass.md
- protonPassAttachment: reference/templates/protonpass-functions/protonPassAttachment.md
- protonPassJSON: reference/templates/protonpass-functions/protonPassJSON.md
- Vault functions:
- vault: reference/templates/vault-functions/vault.md
Expand Down
1 change: 1 addition & 0 deletions internal/cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,7 @@ func newConfig(options ...configOption) (*Config, error) {
"passRaw": c.passRawTemplateFunc,
"passhole": c.passholeTemplateFunc,
"protonPass": c.protonPassTemplateFunc,
"protonPassAttachment": c.protonPassAttachmentTemplateFunc,
"protonPassJSON": c.protonPassJSONTemplateFunc,
"pruneEmptyDicts": c.pruneEmptyDictsTemplateFunc,
"quote": c.quoteTemplateFunc,
Expand Down
46 changes: 44 additions & 2 deletions internal/cmd/protonpasstemplatefuncs.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cmd

import (
"encoding/json"
"io"
"os"
"os/exec"
"strings"
Expand All @@ -11,8 +12,49 @@ import (
)

type protonPassConfig struct {
Command string `json:"command" mapstructure:"command" yaml:"command"`
outputCache map[string][]byte
Command string `json:"command" mapstructure:"command" yaml:"command"`
outputCache map[string][]byte
attachmentCache map[string]string
}

func (c *Config) protonPassAttachmentTemplateFunc(shareID, itemID, attachmentID string) string {
chezmoi.SkipTemplateIf(c.skipSecrets)

key := shareID + "\x00" + itemID + "\x00" + attachmentID
if contents, ok := c.ProtonPass.attachmentCache[key]; ok {
return contents
}

tempDir := mustValue(c.tempDir("chezmoi-proton-pass"))
outputFile := mustValue(os.CreateTemp(tempDir.String(), "attachment-*"))
defer func() { _ = outputFile.Close() }()

args := []string{
"item", "attachment", "download",
"--share-id", shareID,
"--item-id", itemID,
"--attachment-id", attachmentID,
"--output", outputFile.Name(),
}
cmd := exec.Command(c.ProtonPass.Command, args...)
// pass-cli is very chatty. By default, ignore its stdout and stderr, but
// connect them if the --debug flag is passed.
if c.debug {
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
}
output, err := chezmoilog.LogCmdOutput(c.logger, cmd)
if err != nil {
panic(newCmdOutputError(cmd, output, err))
}

contents := string(mustValue(io.ReadAll(outputFile)))
if c.ProtonPass.attachmentCache == nil {
c.ProtonPass.attachmentCache = make(map[string]string)
}
c.ProtonPass.attachmentCache[key] = contents

return contents
}

func (c *Config) protonPassTemplateFunc(item string) string {
Expand Down
43 changes: 43 additions & 0 deletions internal/cmd/testdata/scripts/protonpassattachment.txtar
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
[windows] skip
chmod 755 bin/pass-cli

exec chezmoi execute-template '{{ protonPassAttachment "share-id" "item-id" "attachment-id" }}'
stdout 'Example contents'

-- bin/pass-cli --
#!/bin/sh

[ "$1" = "item" ] || exit 1
[ "$2" = "attachment" ] || exit 1
[ "$3" = "download" ] || exit 1
shift 3

[ "$1" = "--share-id" ] || exit 1
share_id="$2"
shift 2

[ "$1" = "--item-id" ] || exit 1
item_id="$2"
shift 2

[ "$1" = "--attachment-id" ] || exit 1
attachment_id="$2"
shift 2

[ "$1" = "--output" ] || exit 1
output="$2"
shift 2

echo "Downloading attachment..."
echo "Share ID: $share_id"
echo "Item ID: $item_id"
echo "Attachment ID: $attachment_id"
echo "Output path: $output"
echo "Found attachment: example.txt"
echo "Attachment size: 45 bytes"
echo "Attachment type: text/plain"
echo "Attachment chunks: 1"

echo "Example contents" > "$output"

echo "Successfully downloaded attachment to: $output"
Loading