fix: secure profile persistence ownership
This commit is contained in:
@@ -77,6 +77,19 @@ func (a *Application) Dispatch(intent Intent) Result {
|
||||
})
|
||||
if err == nil {
|
||||
a.state = next
|
||||
} else {
|
||||
var committed *profile.CommitError
|
||||
if errors.As(err, &committed) {
|
||||
// Rename committed next before directory durability reporting failed.
|
||||
// Prefer that state even if the required verification reload also fails.
|
||||
a.state = next
|
||||
reloaded, loadErr := a.store.Load()
|
||||
if loadErr != nil {
|
||||
err = errors.Join(err, fmt.Errorf("reload committed Profile configuration: %w", loadErr))
|
||||
} else {
|
||||
a.state = reloaded
|
||||
}
|
||||
}
|
||||
}
|
||||
return Result{Snapshot: a.Snapshot(), Notice: notice, Err: err}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package app_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -71,6 +72,46 @@ func TestProfileConfigurationUsesIsolatedXDGPathOwnerOnlyAndAtomicReplacement(t
|
||||
}
|
||||
}
|
||||
|
||||
type committedErrorStore struct {
|
||||
state profile.State
|
||||
loadCalls int
|
||||
}
|
||||
|
||||
func (s *committedErrorStore) Load() (profile.State, error) {
|
||||
s.loadCalls++
|
||||
return s.state, nil
|
||||
}
|
||||
|
||||
func (s *committedErrorStore) Mutate(change func(profile.State) (profile.State, error)) (profile.State, error) {
|
||||
next, err := change(s.state)
|
||||
if err != nil {
|
||||
return s.state, err
|
||||
}
|
||||
s.state = next
|
||||
return next, &profile.CommitError{Err: errors.New("injected directory sync failure")}
|
||||
}
|
||||
|
||||
func TestApplicationReloadsStateAfterCommittedDurabilityError(t *testing.T) {
|
||||
store := &committedErrorStore{}
|
||||
application, err := app.New(store)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
result := application.Dispatch(app.Intent{Kind: app.CreateProfile, Profile: profile.Profile{
|
||||
Name: "committed", Mode: profile.SingleMode, SingleURL: "http://proxy.example:8080",
|
||||
}})
|
||||
var committed *profile.CommitError
|
||||
if !errors.As(result.Err, &committed) {
|
||||
t.Fatalf("mutation error = %v, want committed durability error", result.Err)
|
||||
}
|
||||
if store.loadCalls != 2 {
|
||||
t.Fatalf("Load calls = %d, want startup plus post-commit reload", store.loadCalls)
|
||||
}
|
||||
if len(result.Snapshot.Profiles) != 1 || result.Snapshot.Profiles[0].Name != "committed" {
|
||||
t.Fatalf("Application snapshot after committed error = %#v", result.Snapshot)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConcurrentApplicationsMutateLatestStateWithoutLostUpdates(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "proxyctl", "profiles.json")
|
||||
first, err := app.New(profile.NewFileStore(path))
|
||||
|
||||
Reference in New Issue
Block a user