fix: reset kwallet session after transport errors
This commit is contained in:
parent
2d97306ea0
commit
0406630e03
2 changed files with 135 additions and 5 deletions
|
|
@ -184,7 +184,7 @@ func (s *walletSession) writeEntry(ctx context.Context, key string, value []byte
|
||||||
|
|
||||||
code, err := s.callInt32(ctx, object, "writeEntry", s.handle, kwalletFolderName, key, value, kwalletAppID)
|
code, err := s.callInt32(ctx, object, "writeEntry", s.handle, kwalletFolderName, key, value, kwalletAppID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return wrapUnavailable("kwallet write failed", err)
|
return s.wrapUnavailable("kwallet write failed", err)
|
||||||
}
|
}
|
||||||
if code != 0 {
|
if code != 0 {
|
||||||
return fmt.Errorf("kwallet write failed with code %d", code)
|
return fmt.Errorf("kwallet write failed with code %d", code)
|
||||||
|
|
@ -204,7 +204,7 @@ func (s *walletSession) readEntry(ctx context.Context, key string) ([]byte, erro
|
||||||
|
|
||||||
hasEntry, err := s.callBool(ctx, object, "hasEntry", s.handle, kwalletFolderName, key, kwalletAppID)
|
hasEntry, err := s.callBool(ctx, object, "hasEntry", s.handle, kwalletFolderName, key, kwalletAppID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, wrapUnavailable("kwallet entry lookup failed", err)
|
return nil, s.wrapUnavailable("kwallet entry lookup failed", err)
|
||||||
}
|
}
|
||||||
if !hasEntry {
|
if !hasEntry {
|
||||||
return nil, fmt.Errorf("%w: key %q", ErrCredentialNotFound, key)
|
return nil, fmt.Errorf("%w: key %q", ErrCredentialNotFound, key)
|
||||||
|
|
@ -212,7 +212,7 @@ func (s *walletSession) readEntry(ctx context.Context, key string) ([]byte, erro
|
||||||
|
|
||||||
value, err := s.callBytes(ctx, object, "readEntry", s.handle, kwalletFolderName, key, kwalletAppID)
|
value, err := s.callBytes(ctx, object, "readEntry", s.handle, kwalletFolderName, key, kwalletAppID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, wrapUnavailable("kwallet read failed", err)
|
return nil, s.wrapUnavailable("kwallet read failed", err)
|
||||||
}
|
}
|
||||||
return value, nil
|
return value, nil
|
||||||
}
|
}
|
||||||
|
|
@ -305,6 +305,12 @@ func (s *walletSession) callBytes(ctx context.Context, object dbusObject, name s
|
||||||
return value, nil
|
return value, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *walletSession) reset() {
|
||||||
|
s.object = nil
|
||||||
|
s.handle = 0
|
||||||
|
s.opened = false
|
||||||
|
}
|
||||||
|
|
||||||
func kwalletMethod(name string) string {
|
func kwalletMethod(name string) string {
|
||||||
return kwalletInterface + "." + name
|
return kwalletInterface + "." + name
|
||||||
}
|
}
|
||||||
|
|
@ -322,8 +328,12 @@ func (e *typedError) Unwrap() []error {
|
||||||
return e.errs
|
return e.errs
|
||||||
}
|
}
|
||||||
|
|
||||||
func wrapUnavailable(message string, err error) error {
|
func (s *walletSession) wrapUnavailable(message string, err error) error {
|
||||||
if err == nil || errors.Is(err, ErrKWalletUnavailable) {
|
if err == nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.reset()
|
||||||
|
if errors.Is(err, ErrKWalletUnavailable) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return &typedError{
|
return &typedError{
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,22 @@ func (c *stubConnection) Object(dest string, path dbus.ObjectPath) dbusObject {
|
||||||
return c.objects[dest+"|"+string(path)]
|
return c.objects[dest+"|"+string(path)]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type rotatingConnection struct {
|
||||||
|
objects []dbusObject
|
||||||
|
index int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *rotatingConnection) Object(string, dbus.ObjectPath) dbusObject {
|
||||||
|
if len(c.objects) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
object := c.objects[c.index]
|
||||||
|
if c.index < len(c.objects)-1 {
|
||||||
|
c.index++
|
||||||
|
}
|
||||||
|
return object
|
||||||
|
}
|
||||||
|
|
||||||
func TestClientIsAvailableReturnsErrorWhenServiceIsMissing(t *testing.T) {
|
func TestClientIsAvailableReturnsErrorWhenServiceIsMissing(t *testing.T) {
|
||||||
client := newClientImpl(newWalletSession(func() (dbusConnection, error) {
|
client := newClientImpl(newWalletSession(func() (dbusConnection, error) {
|
||||||
return &stubConnection{
|
return &stubConnection{
|
||||||
|
|
@ -294,3 +310,107 @@ func TestClientReadEntryMapsTransportFailuresToUnavailable(t *testing.T) {
|
||||||
t.Fatalf("expected wrapped transport error, got %v", err)
|
t.Fatalf("expected wrapped transport error, got %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestClientWriteEntryReopensAfterMappedTransportFailure(t *testing.T) {
|
||||||
|
firstObject := &stubObject{
|
||||||
|
responses: map[string][]stubCall{
|
||||||
|
kwalletMethod("isEnabled"): {{body: []any{true}}},
|
||||||
|
kwalletMethod("networkWallet"): {{body: []any{"kdewallet"}}},
|
||||||
|
kwalletMethod("open"): {{body: []any{int32(42)}}},
|
||||||
|
kwalletMethod("hasFolder"): {{body: []any{true}}},
|
||||||
|
kwalletMethod("writeEntry"): {{err: errors.New("transport closed")}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
secondObject := &stubObject{
|
||||||
|
responses: map[string][]stubCall{
|
||||||
|
kwalletMethod("isEnabled"): {{body: []any{true}}},
|
||||||
|
kwalletMethod("networkWallet"): {{body: []any{"kdewallet"}}},
|
||||||
|
kwalletMethod("open"): {{body: []any{int32(43)}}},
|
||||||
|
kwalletMethod("hasFolder"): {{body: []any{true}}},
|
||||||
|
kwalletMethod("writeEntry"): {{body: []any{int32(0)}}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
client := newClientImpl(newWalletSession(func() (dbusConnection, error) {
|
||||||
|
return &rotatingConnection{objects: []dbusObject{firstObject, secondObject}}, nil
|
||||||
|
}))
|
||||||
|
|
||||||
|
err := client.WriteEntry(context.Background(), "default", []byte("payload"))
|
||||||
|
if !errors.Is(err, ErrKWalletUnavailable) {
|
||||||
|
t.Fatalf("expected unavailable error, got %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := client.WriteEntry(context.Background(), "default", []byte("payload")); err != nil {
|
||||||
|
t.Fatalf("expected retry to succeed, got %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
firstOpenCalls := 0
|
||||||
|
for _, call := range firstObject.calls {
|
||||||
|
if call.method == kwalletMethod("open") {
|
||||||
|
firstOpenCalls++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
secondOpenCalls := 0
|
||||||
|
for _, call := range secondObject.calls {
|
||||||
|
if call.method == kwalletMethod("open") {
|
||||||
|
secondOpenCalls++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if firstOpenCalls != 1 || secondOpenCalls != 1 {
|
||||||
|
t.Fatalf("expected reopen on retry, got first=%d second=%d", firstOpenCalls, secondOpenCalls)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientReadEntryReopensAfterMappedTransportFailure(t *testing.T) {
|
||||||
|
firstObject := &stubObject{
|
||||||
|
responses: map[string][]stubCall{
|
||||||
|
kwalletMethod("isEnabled"): {{body: []any{true}}},
|
||||||
|
kwalletMethod("networkWallet"): {{body: []any{"kdewallet"}}},
|
||||||
|
kwalletMethod("open"): {{body: []any{int32(42)}}},
|
||||||
|
kwalletMethod("hasFolder"): {{body: []any{true}}},
|
||||||
|
kwalletMethod("hasEntry"): {{body: []any{true}}},
|
||||||
|
kwalletMethod("readEntry"): {{err: errors.New("transport closed")}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
secondObject := &stubObject{
|
||||||
|
responses: map[string][]stubCall{
|
||||||
|
kwalletMethod("isEnabled"): {{body: []any{true}}},
|
||||||
|
kwalletMethod("networkWallet"): {{body: []any{"kdewallet"}}},
|
||||||
|
kwalletMethod("open"): {{body: []any{int32(43)}}},
|
||||||
|
kwalletMethod("hasFolder"): {{body: []any{true}}},
|
||||||
|
kwalletMethod("hasEntry"): {{body: []any{true}}},
|
||||||
|
kwalletMethod("readEntry"): {{body: []any{[]byte("payload")}}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
client := newClientImpl(newWalletSession(func() (dbusConnection, error) {
|
||||||
|
return &rotatingConnection{objects: []dbusObject{firstObject, secondObject}}, nil
|
||||||
|
}))
|
||||||
|
|
||||||
|
_, err := client.ReadEntry(context.Background(), "default")
|
||||||
|
if !errors.Is(err, ErrKWalletUnavailable) {
|
||||||
|
t.Fatalf("expected unavailable error, got %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
value, err := client.ReadEntry(context.Background(), "default")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected retry to succeed, got %v", err)
|
||||||
|
}
|
||||||
|
if string(value) != "payload" {
|
||||||
|
t.Fatalf("unexpected retry payload: %q", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
firstOpenCalls := 0
|
||||||
|
for _, call := range firstObject.calls {
|
||||||
|
if call.method == kwalletMethod("open") {
|
||||||
|
firstOpenCalls++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
secondOpenCalls := 0
|
||||||
|
for _, call := range secondObject.calls {
|
||||||
|
if call.method == kwalletMethod("open") {
|
||||||
|
secondOpenCalls++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if firstOpenCalls != 1 || secondOpenCalls != 1 {
|
||||||
|
t.Fatalf("expected reopen on retry, got first=%d second=%d", firstOpenCalls, secondOpenCalls)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue