feat: Add ListUsers method to retrieve usernames for a service (#133)#135
feat: Add ListUsers method to retrieve usernames for a service (#133)#135ayoub3bidi wants to merge 2 commits intozalando:masterfrom
Conversation
…do#133) Signed-off-by: Ayoub Abidi <mrayoubabidi@gmail.com>
51a2f58 to
ac1ec0c
Compare
keyring_darwin.go
Outdated
| return []string{}, nil | ||
| } | ||
|
|
||
| out, err := exec.Command(execPathKeychain, "dump-keyring").CombinedOutput() |
There was a problem hiding this comment.
/usr/bin/security dump-keyring
security: unknown command "dump-keyring"There was a problem hiding this comment.
package main
import (
"bufio"
"bytes"
"fmt"
"io"
"os"
"os/exec"
"strings"
)
type entry struct {
keychain string
svce string
acct string
}
func main() {
service := os.Args[1]
b, err := exec.Command("/usr/bin/security", "default-keychain").Output()
if err != nil {
panic(err)
}
defaultKeychain := strings.Trim(string(b), " \"\n")
b, err = exec.Command("/usr/bin/security", "dump-keychain").Output()
if err != nil {
panic(err)
}
var entries []*entry
var e *entry
valueOf := func(s, prefix string) string {
return strings.Trim(strings.TrimPrefix(s, prefix), "\"\n")
}
r := bufio.NewReader(bytes.NewReader(b))
for {
line, err := r.ReadString('\n')
if err != nil && err != io.EOF {
panic(err)
}
switch {
case strings.HasPrefix(line, `keychain: `):
e = new(entry)
e.keychain = valueOf(line, `keychain: `)
entries = append(entries, e)
case strings.HasPrefix(line, ` "svce"<blob>=`):
e.svce = valueOf(line, ` "svce"<blob>=`)
case strings.HasPrefix(line, ` "acct"<blob>=`):
e.acct = valueOf(line, ` "acct"<blob>=`)
}
if err == io.EOF {
break
}
}
for _, e := range entries {
if e.keychain == defaultKeychain && e.svce == service {
fmt.Printf("%s\n", e.acct)
}
}
}
keyring_darwin.go
Outdated
| lines := strings.Split(string(out), "\n") | ||
|
|
||
| // Parse dump-keyring output looking for generic passwords matching our service | ||
| // Format: keychain: "/Users/username/Library/Keychains/login.keychain-db" |
There was a problem hiding this comment.
There are multiple keychains so it should probably call /usr/bin/security default-keychain to get the name of the default one.
|
@ayoub3bidi thanks for the PR for me looks like a nice addition. |
|
@AlexanderYastrebov @szuecs thanks for the review. I've addressed the findings:
Happy to adjust if you have further feedback. |
|
@ayoub3bidi you need to sign-off your commits (DCO check) |
- Use dump-keychain instead of dump-keyring - Filter by default keychain when multiple exist - Add ExampleListUsers with MockInit for godoc - Add MockRestore for provider restoration Signed-off-by: Ayoub Abidi <mrayoubabidi@gmail.com>
1da1493 to
24cf490
Compare
Implements a new
ListUsers(service string) ([]string, error)method that retrieves all usernames associated with a given service without exposing the secret values. This addresses the feature request to allow CLI tools and applications to discover which credentials are stored for a service.Motivation
As described in #133, when using
go-keyringin CLI tools that manage multiple third-party tokens (e.g., GitHub, AWS, etc.), users need a way to see which secrets the tool is managing without manually checking their system keychain. This feature enables better UX by allowing applications to list available credentials.Example use case: A CLI tool with service name
my-clithat manages tokens for multiple providers (GitHub, GitLab, AWS). The tool can now callListUsers("my-cli")to show users which providers they have configured.Changes
Core Interface
ListUsers(service string) ([]string, error)toKeyringinterfaceListUsersfunctionPlatform Implementations
macOS (keyring_darwin.go)
security dump-keyringoutput to extract account namesLinux/Unix (keyring_unix.go)
findServiceItemshelper to query D-Bus Secret ServiceGetItemAttributesto secret_service/secret_service.goWindows (keyring_windows.go)
wincred.List()to enumerate credentialsDeleteAllimplementation patternMock & Fallback
ErrUnsupportedPlatformTests
Added four comprehensive test cases to keyring_test.go:
TestListUsers: Multiple users per serviceTestListUsersEmpty: Non-existent service returns empty sliceTestListUsersSingleUser: Single user edge caseTestListUsersMultipleServices: Service isolation verificationTest Results
Usage Example
Backward Compatibility
No breaking changes
Implementation Notes
Naming Decision
As suggested by @szuecs in #133, the method is named
ListUsersinstead ofListfor clarity and to avoid ambiguity about what is being listed.Platform-Specific Behavior
Security
Checklist