Skip to content

Commit

Permalink
feat: add DeleteAll methods to keyring
Browse files Browse the repository at this point in the history
  • Loading branch information
avallete committed Sep 10, 2024
1 parent de351c5 commit 3ad9698
Show file tree
Hide file tree
Showing 8 changed files with 160 additions and 0 deletions.
7 changes: 7 additions & 0 deletions keyring.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ type Keyring interface {
Get(service, user string) (string, error)
// Delete secret from keyring.
Delete(service, user string) error
// DeleteAll deletes all secrets for a given service
DeleteAll(service string) error
}

// Set password in keyring for user.
Expand All @@ -41,3 +43,8 @@ func Get(service, user string) (string, error) {
func Delete(service, user string) error {
return provider.Delete(service, user)
}

// DeleteAll deletes all secrets for a given service
func DeleteAll(service string) error {
return provider.DeleteAll(service)
}
18 changes: 18 additions & 0 deletions keyring_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,24 @@ func (k macOSXKeychain) Delete(service, username string) error {
return err
}

// DeleteAll deletes all secrets for a given service
func (k macOSXKeychain) DeleteAll(service string) error {
// Delete each secret in a while loop until there is no more left
// under the service
for {
out, err := exec.Command(
execPathKeychain,
"delete-generic-password",
"-s", service).CombinedOutput()
if strings.Contains(string(out), "could not be found") {
return nil
} else if err != nil {
return err
}
}

}

func init() {
provider = macOSXKeychain{}
}
4 changes: 4 additions & 0 deletions keyring_fallback.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,7 @@ func (fallbackServiceProvider) Get(service, user string) (string, error) {
func (fallbackServiceProvider) Delete(service, user string) error {
return ErrUnsupportedPlatform
}

func (fallbackServiceProvider) DeleteAll(service string) error {
return ErrUnsupportedPlatform
}
9 changes: 9 additions & 0 deletions keyring_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,15 @@ func (m *mockProvider) Delete(service, user string) error {
return ErrNotFound
}

// DeleteAll deletes all secrets for a given service
func (m *mockProvider) DeleteAll(service string) error {
if m.mockError != nil {
return m.mockError
}
delete(m.mockStore, service)
return nil
}

// MockInit sets the provider to a mocked memory store
func MockInit() {
provider = &mockProvider{}
Expand Down
35 changes: 35 additions & 0 deletions keyring_mock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,41 @@ func TestMockWithError(t *testing.T) {
assertError(t, err, mp.mockError)
}

// TestMockDeleteAll tests deleting all secrets for a given service.
func TestMockDeleteAll(t *testing.T) {
mp := mockProvider{}

// Set up multiple secrets for the same service
err := mp.Set(service, user, password)
if err != nil {
t.Errorf("Should not fail, got: %s", err)
}

err = mp.Set(service, user+"2", password+"2")
if err != nil {
t.Errorf("Should not fail, got: %s", err)
}

// Delete all secrets for the service
err = mp.DeleteAll(service)
if err != nil {
t.Errorf("Should not fail, got: %s", err)
}

// Verify that all secrets for the service are deleted
_, err = mp.Get(service, user)
assertError(t, err, ErrNotFound)

_, err = mp.Get(service, user+"2")
assertError(t, err, ErrNotFound)

// Verify that DeleteAll on an empty service doesn't cause an error
err = mp.DeleteAll(service)
if err != nil {
t.Errorf("Should not fail on empty service, got: %s", err)
}
}

func assertError(t *testing.T, err error, expected error) {
if err != expected {
t.Errorf("Expected error %s, got %s", expected, err)
Expand Down
37 changes: 37 additions & 0 deletions keyring_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,40 @@ func TestDeleteNonExisting(t *testing.T) {
t.Errorf("Expected error ErrNotFound, got %s", err)
}
}

// TestDeleteAll tests deleting all secrets for a given service.
func TestDeleteAll(t *testing.T) {
// Set up multiple secrets for the same service
err := Set(service, user, password)
if err != nil {
t.Errorf("Should not fail, got: %s", err)
}

err = Set(service, user+"2", password+"2")
if err != nil {
t.Errorf("Should not fail, got: %s", err)
}

// Delete all secrets for the service
err = DeleteAll(service)
if err != nil {
t.Errorf("Should not fail, got: %s", err)
}

// Verify that all secrets for the service are deleted
_, err = Get(service, user)
if err != ErrNotFound {
t.Errorf("Expected error ErrNotFound, got %s", err)
}

_, err = Get(service, user+"2")
if err != ErrNotFound {
t.Errorf("Expected error ErrNotFound, got %s", err)
}

// Verify that DeleteAll on an empty service doesn't cause an error
err = DeleteAll(service)
if err != nil {
t.Errorf("Should not fail on empty service, got: %s", err)
}
}
21 changes: 21 additions & 0 deletions keyring_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,27 @@ func (s secretServiceProvider) Delete(service, user string) error {
return svc.Delete(item)
}

// DeleteAll deletes all secrets for a given service
func (s secretServiceProvider) DeleteAll(service string) error {
svc, err := ss.NewSecretService()
if err != nil {
return err
}
for {
item, err := s.findItem(svc, service, "")
if err != nil {
if err == ErrNotFound {
return nil
}
return err
}
err = svc.Delete(item)
if err != nil {
return err
}
}
}

func init() {
provider = secretServiceProvider{}
}
29 changes: 29 additions & 0 deletions keyring_windows.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package keyring

import (
"strings"
"syscall"

"github.com/danieljoos/wincred"
Expand Down Expand Up @@ -59,6 +60,34 @@ func (k windowsKeychain) Delete(service, username string) error {
return cred.Delete()
}

func (k windowsKeychain) DeleteAll(service string) error {
creds, err := wincred.List()
if err != nil {
return err
}

prefix := k.credName(service, "")
deletedCount := 0

for _, cred := range creds {
if strings.HasPrefix(cred.TargetName, prefix) {
genericCred, err := wincred.GetGenericCredential(cred.TargetName)
if err != nil {
if err != syscall.ERROR_NOT_FOUND {
return err
}
} else {
err := genericCred.Delete()
if err != nil {
return err
}
deletedCount++
}
}
}
return nil
}

// credName combines service and username to a single string.
func (k windowsKeychain) credName(service, username string) string {
return service + ":" + username
Expand Down

0 comments on commit 3ad9698

Please sign in to comment.