gopass/internal/action/config_test.go
google-labs-jules[bot] 8c60b17c24
feat(age): Add unlock command to age agent (#3244)
* feat(age): Add unlock command to age agent

This change introduces a proper lock/unlock mechanism for the age agent.

The issue was that after locking the agent with `gopass age lock`, there was no way to unlock it without restarting the agent. This made the lock command mostly useless.

This change introduces a new `unlock` command for the agent and a `locked` state.

- The `lock` command now sets a `locked` flag to `true` in addition to clearing identities.
- The `decrypt` function in the agent now checks this `locked` flag and returns an error if the agent is locked.
- When the gopass client receives the "agent is locked" error, it will ask the user for their passphrase, reload the identities, and send them to the agent.
- A new `gopass age agent unlock` CLI command is added to trigger this new functionality.
- The `gopass age agent status` command is enhanced to report whether the agent is locked.
- The old top-level `gopass age lock` command is hidden, and a new `gopass age agent lock` command is introduced for consistency.

Fixes #3242

* feat(age): Add unlock command to age agent

This change introduces a proper lock/unlock mechanism for the age agent.

The issue was that after locking the agent with `gopass age lock`, there was no way to unlock it without restarting the agent. This made the lock command mostly useless.

This change introduces a new `unlock` command for the agent and a `locked` state.

- The `lock` command now sets a `locked` flag to `true` in addition to clearing identities.
- The `decrypt` function in the agent now checks this `locked` flag and returns an error if the agent is locked.
- When the gopass client receives the "agent is locked" error, it will ask the user for their passphrase, reload the identities, and send them to the agent.
- A new `gopass age agent unlock` CLI command is added to trigger this new functionality.
- The `gopass age agent status` command is enhanced to report whether the agent is locked.
- The old top-level `gopass age lock` command is hidden, and a new `gopass age agent lock` command is introduced for consistency.

I have also addressed the PR comment about the import alias. I have removed the alias and used a dot import instead to avoid the name collision.

Fixes #3242

* feat(age): Add unlock command to age agent

This change introduces a proper lock/unlock mechanism for the age agent.

The issue was that after locking the agent with `gopass age lock`, there was no way to unlock it without restarting the agent. This made the lock command mostly useless.

This change introduces a new `unlock` command for the agent and a `locked` state.

- The `lock` command now sets a `locked` flag to `true` in addition to clearing identities.
- The `decrypt` function in the agent now checks this `locked` flag and returns an error if the agent is locked.
- When the gopass client receives the "agent is locked" error, it will ask the user for their passphrase, reload the identities, and send them to the agent.
- A new `gopass age agent unlock` CLI command is added to trigger this new functionality.
- The `gopass age agent status` command is enhanced to report whether the agent is locked.
- The old top-level `gopass age lock` command is hidden, and a new `gopass age agent lock` command is introduced for consistency.

To avoid name collisions with the imported `filippo.io/age` package, the local `age` package has been renamed to `agecrypto`.

Fixes #3242

* feat(age): Add auto-lock feature to age agent

This change introduces an auto-lock feature for the age agent. The agent will now automatically lock itself after a configurable period of inactivity.

This change also includes the initial fix for issue #3242, which introduced a proper lock/unlock mechanism for the age agent.

- A new config option `age.agent-timeout` is added to specify the inactivity timeout in seconds.
- The agent now has a timer that is reset on every successful decryption operation.
- If the timer expires, the agent locks itself.
- A new `set-timeout` command is added to the agent protocol to configure the timeout.
- The gopass client sends the timeout to the agent when it starts or when it unlocks the agent.
- A new test `TestAgentAutoLock` is added to verify the new functionality.

To avoid name collisions with the imported `filippo.io/age` package, the local `age` package has been renamed to `agecrypto`.

Fixes #3242

* [fix] Fix lint issues

Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>

---------

Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Co-authored-by: Dominik Schulz <dominik.schulz@gauner.org>
2025-09-20 17:09:12 +02:00

147 lines
3.4 KiB
Go

package action
import (
"bytes"
"os"
"strings"
"testing"
"github.com/gopasspw/gopass/internal/config"
"github.com/gopasspw/gopass/internal/out"
"github.com/gopasspw/gopass/pkg/ctxutil"
"github.com/gopasspw/gopass/tests/gptest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestConfig(t *testing.T) {
u := gptest.NewUnitTester(t)
ctx := config.NewContextInMemory()
ctx = ctxutil.WithInteractive(ctx, false)
act, err := newMock(ctx, u.StoreDir(""))
require.NoError(t, err)
require.NotNil(t, act)
ctx = act.cfg.WithConfig(ctx)
buf := &bytes.Buffer{}
out.Stdout = buf
stdout = buf
defer func() {
out.Stdout = os.Stdout
stdout = os.Stdout
}()
t.Run("display config", func(t *testing.T) {
defer buf.Reset()
c := gptest.CliCtx(ctx, t)
require.NoError(t, act.Config(c))
want := `age.agent-enabled = false
age.agent-timeout = 0
core.autoimport = true
core.autopush = true
core.autosync = true
core.cliptimeout = 45
core.exportkeys = true
core.follow-references = false
core.nopager = true
core.notifications = true
generate.autoclip = true
`
want += "mounts.path = " + u.StoreDir("") + "\n" +
"pwgen.xkcd-lang = en\n"
assert.Equal(t, want, buf.String())
})
t.Run("set valid config value", func(t *testing.T) {
defer buf.Reset()
require.NoError(t, act.setConfigValue(ctx, "", "core.nopager", "true"))
// should print accepted config value
assert.Equal(t, "true", strings.TrimSpace(buf.String()), "action.setConfigValue")
})
t.Run("set invalid config value", func(t *testing.T) {
defer buf.Reset()
require.Error(t, act.setConfigValue(ctx, "", "foobar", "true"))
})
t.Run("print single config value", func(t *testing.T) {
defer buf.Reset()
act.printConfigValues(ctx, "", "core.nopager")
want := "true"
assert.Equal(t, want, strings.TrimSpace(buf.String()), "action.printConfigValues")
})
t.Run("print all config values", func(t *testing.T) {
defer buf.Reset()
act.printConfigValues(ctx, "")
want := `age.agent-enabled = false
age.agent-timeout = 0
core.autoimport = true
core.autopush = true
core.autosync = true
core.cliptimeout = 45
core.exportkeys = true
core.follow-references = false
core.nopager = true
core.notifications = true
generate.autoclip = true
`
want += "mounts.path = " + u.StoreDir("") + "\n" +
"pwgen.xkcd-lang = en\n"
assert.Equal(t, want, buf.String(), "action.printConfigValues")
})
t.Run("show autoimport value", func(t *testing.T) {
defer buf.Reset()
c := gptest.CliCtx(ctx, t, "core.autoimport")
require.NoError(t, act.Config(c))
assert.Equal(t, "true", strings.TrimSpace(buf.String()))
})
t.Run("disable autoimport", func(t *testing.T) {
defer buf.Reset()
c := gptest.CliCtx(ctx, t, "core.autoimport", "false")
require.NoError(t, act.Config(c))
assert.Equal(t, "false", strings.TrimSpace(buf.String()))
})
t.Run("complete config items", func(t *testing.T) {
defer buf.Reset()
act.ConfigComplete(gptest.CliCtx(ctx, t))
want := `age.agent-enabled
age.agent-timeout
core.autoimport
core.autopush
core.autosync
core.cliptimeout
core.exportkeys
core.follow-references
core.nopager
core.notifications
generate.autoclip
mounts.path
pwgen.xkcd-lang
`
assert.Equal(t, want, buf.String())
})
t.Run("set autoimport to invalid value", func(t *testing.T) {
defer buf.Reset()
c := gptest.CliCtx(ctx, t, "autoimport", "false", "42")
require.Error(t, act.Config(c))
})
}