mirror of
https://github.com/docker/docker-credential-helpers.git
synced 2026-06-13 16:01:28 +05:30
Merge pull request #18 from avaid96/listing
Implementing listing functionality across OSX, Linux and Windows
This commit is contained in:
+13
-2
@@ -55,11 +55,10 @@ func Get(program ProgramFunc, serverURL string) (*credentials.Credentials, error
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Erase executes a program to remove the server credentails from the native store.
|
||||
// Erase executes a program to remove the server credentials from the native store.
|
||||
func Erase(program ProgramFunc, serverURL string) error {
|
||||
cmd := program("erase")
|
||||
cmd.Input(strings.NewReader(serverURL))
|
||||
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
t := strings.TrimSpace(string(out))
|
||||
@@ -68,3 +67,15 @@ func Erase(program ProgramFunc, serverURL string) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// List executes a program to remove the server credentials from the native store.
|
||||
func List(program ProgramFunc) error {
|
||||
cmd := program("list")
|
||||
cmd.Input(strings.NewReader("garbage"))
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
t := strings.TrimSpace(string(out))
|
||||
return fmt.Errorf("error listing credentials - err: %v, out: `%s`", err, t)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -70,6 +70,9 @@ func (m *mockProgram) Output() ([]byte, error) {
|
||||
default:
|
||||
return []byte("error storing credentials"), errProgramExited
|
||||
}
|
||||
case "list":
|
||||
return []byte(`{"Path":"e237574ae22fd53ddb9490dc1f72139946fd5372d42ba54d1eeb3ae5068fd22b","Username":"http://example.com/collections\u003cnotary_key\u003eSnapshot"}`), nil
|
||||
|
||||
}
|
||||
|
||||
return []byte(fmt.Sprintf("unknown argument %q with %q", m.arg, inS)), errProgramExited
|
||||
@@ -190,3 +193,9 @@ func TestErase(t *testing.T) {
|
||||
t.Fatalf("Expected error for server %s, got nil", invalidServerAddress)
|
||||
}
|
||||
}
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
if err := List(mockProgramFn); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,11 @@ type Credentials struct {
|
||||
Secret string
|
||||
}
|
||||
|
||||
type KeyData struct {
|
||||
Path string
|
||||
Username string
|
||||
}
|
||||
|
||||
// Serve initializes the credentials helper and parses the action argument.
|
||||
// This function is designed to be called from a command line interface.
|
||||
// It uses os.Args[1] as the key for the action.
|
||||
@@ -25,7 +30,7 @@ type Credentials struct {
|
||||
func Serve(helper Helper) {
|
||||
var err error
|
||||
if len(os.Args) != 2 {
|
||||
err = fmt.Errorf("Usage: %s <store|get|erase>", os.Args[0])
|
||||
err = fmt.Errorf("Usage: %s <store|get|erase|list>", os.Args[0])
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
@@ -47,6 +52,8 @@ func HandleCommand(helper Helper, key string, in io.Reader, out io.Writer) error
|
||||
return Get(helper, in, out)
|
||||
case "erase":
|
||||
return Erase(helper, in)
|
||||
case "list":
|
||||
return List(helper, out)
|
||||
}
|
||||
return fmt.Errorf("Unknown credential action `%s`", key)
|
||||
}
|
||||
@@ -127,3 +134,26 @@ func Erase(helper Helper, reader io.Reader) error {
|
||||
|
||||
return helper.Delete(serverURL)
|
||||
}
|
||||
|
||||
//List returns all the serverURLs of keys in
|
||||
//the OS store as a list of strings
|
||||
func List(helper Helper, writer io.Writer) error {
|
||||
paths, accts, err := helper.List()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
keyDataList := []KeyData{}
|
||||
for index := 0; index < len(paths); index++ {
|
||||
keyDataObj := KeyData{
|
||||
Path: paths[index],
|
||||
Username: accts[index],
|
||||
}
|
||||
keyDataList = append([]KeyData{keyDataObj}, keyDataList...)
|
||||
}
|
||||
buffer := new(bytes.Buffer)
|
||||
if err := json.NewEncoder(buffer).Encode(keyDataList); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprint(writer, buffer.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -36,6 +36,11 @@ func (m *memoryStore) Get(serverURL string) (string, string, error) {
|
||||
return c.Username, c.Secret, nil
|
||||
}
|
||||
|
||||
func (m *memoryStore) List() ([]string, []string, error) {
|
||||
//Simply a placeholder to let memoryStore be a valid implementation of Helper interface
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
func TestStore(t *testing.T) {
|
||||
serverURL := "https://index.docker.io/v1/"
|
||||
creds := &Credentials{
|
||||
@@ -138,3 +143,17 @@ func TestErase(t *testing.T) {
|
||||
t.Fatal("expected error getting missing creds, got empty")
|
||||
}
|
||||
}
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
//This tests that there is proper input an output into the byte stream
|
||||
//Individual stores are very OS specific and have been tested in osxkeychain and secretservice respectively
|
||||
out := new(bytes.Buffer)
|
||||
h := newMemoryStore()
|
||||
if err := List(h, out); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
//testing that there is an output
|
||||
if out.Len() == 0 {
|
||||
t.Fatalf("expected output in the writer, got %d", 0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,4 +9,8 @@ type Helper interface {
|
||||
// Get retrieves credentials from the store.
|
||||
// It returns username and secret as strings.
|
||||
Get(serverURL string) (string, string, error)
|
||||
// List returns the serverURLs of keys and their
|
||||
// associated usernames from the OS store as a
|
||||
// list of strings
|
||||
List() ([]string, []string, error)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
#include "osxkeychain_darwin.h"
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
char *get_error(OSStatus status) {
|
||||
char *buf = malloc(128);
|
||||
@@ -96,3 +99,77 @@ char *keychain_delete(struct Server *server) {
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char * CFStringToCharArr(CFStringRef aString) {
|
||||
if (aString == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
CFIndex length = CFStringGetLength(aString);
|
||||
CFIndex maxSize =
|
||||
CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1;
|
||||
char *buffer = (char *)malloc(maxSize);
|
||||
if (CFStringGetCString(aString, buffer, maxSize,
|
||||
kCFStringEncodingUTF8)) {
|
||||
return buffer;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *keychain_list(char *** paths, char *** accts, unsigned int *list_l) {
|
||||
CFMutableDictionaryRef query = CFDictionaryCreateMutable (NULL, 1, NULL, NULL);
|
||||
CFDictionaryAddValue(query, kSecClass, kSecClassInternetPassword);
|
||||
CFDictionaryAddValue(query, kSecReturnAttributes, kCFBooleanTrue);
|
||||
CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitAll);
|
||||
//Use this query dictionary
|
||||
CFTypeRef result= NULL;
|
||||
OSStatus status = SecItemCopyMatching(
|
||||
query,
|
||||
&result);
|
||||
//Ran a search and store the results in result
|
||||
if (status) {
|
||||
return get_error(status);
|
||||
}
|
||||
int numKeys = CFArrayGetCount(result);
|
||||
*paths = (char **) malloc((int)sizeof(char *)*numKeys);
|
||||
*accts = (char **) malloc((int)sizeof(char *)*numKeys);
|
||||
//result is of type CFArray
|
||||
for(int i=0; i<numKeys; i++) {
|
||||
CFDictionaryRef currKey = CFArrayGetValueAtIndex(result,i);
|
||||
if (CFDictionaryContainsKey(currKey, CFSTR("path"))) {
|
||||
//Even if a key is stored without an account, Apple defaults it to null so these arrays will be of the same length
|
||||
CFStringRef pathTmp = CFDictionaryGetValue(currKey, CFSTR("path"));
|
||||
CFStringRef acctTmp = CFDictionaryGetValue(currKey, CFSTR("acct"));
|
||||
if (acctTmp == NULL) {
|
||||
acctTmp = CFSTR("account not defined");
|
||||
}
|
||||
char * path = (char *) malloc(CFStringGetLength(pathTmp)+1);
|
||||
path = CFStringToCharArr(pathTmp);
|
||||
path[strlen(path)] = '\0';
|
||||
char * acct = (char *) malloc(CFStringGetLength(acctTmp)+1);
|
||||
acct = CFStringToCharArr(acctTmp);
|
||||
acct[strlen(acct)] = '\0';
|
||||
//We now have all we need, username and servername. Now export this to .go
|
||||
(*paths)[i] = (char *) malloc(sizeof(char)*(strlen(path)+1));
|
||||
memcpy((*paths)[i], path, sizeof(char)*(strlen(path)+1));
|
||||
(*accts)[i] = (char *) malloc(sizeof(char)*(strlen(acct)+1));
|
||||
memcpy((*accts)[i], acct, sizeof(char)*(strlen(acct)+1));
|
||||
}
|
||||
else {
|
||||
char * path = "0";
|
||||
char * acct = "0";
|
||||
(*paths)[i] = (char *) malloc(sizeof(char)*(strlen(path)));
|
||||
memcpy((*paths)[i], path, sizeof(char)*(strlen(path)));
|
||||
(*accts)[i] = (char *) malloc(sizeof(char)*(strlen(acct)));
|
||||
memcpy((*accts)[i], acct, sizeof(char)*(strlen(acct)));
|
||||
}
|
||||
}
|
||||
*list_l = numKeys;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void freeListData(char *** data, unsigned int length) {
|
||||
for(int i=0; i<length; i++) {
|
||||
free((*data)[i]);
|
||||
}
|
||||
free(*data);
|
||||
}
|
||||
|
||||
@@ -10,12 +10,11 @@ package osxkeychain
|
||||
import "C"
|
||||
import (
|
||||
"errors"
|
||||
"github.com/docker/docker-credential-helpers/credentials"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/docker/docker-credential-helpers/credentials"
|
||||
)
|
||||
|
||||
// errCredentialsNotFound is the specific error message returned by OS X
|
||||
@@ -83,7 +82,6 @@ func (h Osxkeychain) Get(serverURL string) (string, string, error) {
|
||||
if errMsg != nil {
|
||||
defer C.free(unsafe.Pointer(errMsg))
|
||||
goMsg := C.GoString(errMsg)
|
||||
|
||||
if goMsg == errCredentialsNotFound {
|
||||
return "", "", credentials.NewErrCredentialsNotFound()
|
||||
}
|
||||
@@ -96,6 +94,41 @@ func (h Osxkeychain) Get(serverURL string) (string, string, error) {
|
||||
return user, pass, nil
|
||||
}
|
||||
|
||||
func (h Osxkeychain) List() ([]string, []string, error) {
|
||||
var pathsC **C.char
|
||||
defer C.free(unsafe.Pointer(pathsC))
|
||||
var acctsC **C.char
|
||||
defer C.free(unsafe.Pointer(acctsC))
|
||||
var listLenC C.uint
|
||||
errMsg := C.keychain_list(&pathsC, &acctsC, &listLenC)
|
||||
if errMsg != nil {
|
||||
defer C.free(unsafe.Pointer(errMsg))
|
||||
goMsg := C.GoString(errMsg)
|
||||
return nil, nil, errors.New(goMsg)
|
||||
}
|
||||
var listLen int
|
||||
listLen = int(listLenC)
|
||||
pathTmp := (*[1 << 30]*C.char)(unsafe.Pointer(pathsC))[:listLen:listLen]
|
||||
acctTmp := (*[1 << 30]*C.char)(unsafe.Pointer(acctsC))[:listLen:listLen]
|
||||
//taking the array of c strings into go while ignoring all the stuff irrelevant to credentials-helper
|
||||
paths := make([]string, listLen)
|
||||
accts := make([]string, listLen)
|
||||
at := 0
|
||||
for i := 0; i < listLen; i++ {
|
||||
if C.GoString(pathTmp[i]) == "0" {
|
||||
continue
|
||||
}
|
||||
paths[at] = C.GoString(pathTmp[i])
|
||||
accts[at] = C.GoString(acctTmp[i])
|
||||
at = at + 1
|
||||
}
|
||||
paths = paths[:at]
|
||||
accts = accts[:at]
|
||||
C.freeListData(&pathsC, listLenC)
|
||||
C.freeListData(&acctsC, listLenC)
|
||||
return paths, accts, nil
|
||||
}
|
||||
|
||||
func splitServer(serverURL string) (*C.struct_Server, error) {
|
||||
u, err := url.Parse(serverURL)
|
||||
if err != nil {
|
||||
|
||||
@@ -10,3 +10,5 @@ struct Server {
|
||||
char *keychain_add(struct Server *server, char *username, char *secret);
|
||||
char *keychain_get(struct Server *server, unsigned int *username_l, char **username, unsigned int *secret_l, char **secret);
|
||||
char *keychain_delete(struct Server *server);
|
||||
char *keychain_list(char *** data, char *** accts, unsigned int *list_l);
|
||||
void freeListData(char *** data, unsigned int length);
|
||||
@@ -1,9 +1,8 @@
|
||||
package osxkeychain
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker-credential-helpers/credentials"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestOSXKeychainHelper(t *testing.T) {
|
||||
@@ -12,7 +11,11 @@ func TestOSXKeychainHelper(t *testing.T) {
|
||||
Username: "foobar",
|
||||
Secret: "foobarbaz",
|
||||
}
|
||||
|
||||
creds1 := &credentials.Credentials{
|
||||
ServerURL: "https://foobar.docker.io:2376/v2",
|
||||
Username: "foobarbaz",
|
||||
Secret: "foobar",
|
||||
}
|
||||
helper := Osxkeychain{}
|
||||
if err := helper.Add(creds); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -31,6 +34,21 @@ func TestOSXKeychainHelper(t *testing.T) {
|
||||
t.Fatalf("expected %s, got %s\n", "foobarbaz", secret)
|
||||
}
|
||||
|
||||
paths, accts, err := helper.List()
|
||||
if err != nil || len(paths) == 0 || len(accts) == 0 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
helper.Add(creds1)
|
||||
defer helper.Delete(creds1.ServerURL)
|
||||
newpaths, newaccts, err := helper.List()
|
||||
if len(newpaths)-len(paths) != 1 || len(newaccts)-len(accts) != 1 {
|
||||
if err == nil {
|
||||
t.Fatalf("Error: len(newpaths): %d, len(paths): %d\n len(newaccts): %d, len(accts): %d\n Error= %s", len(newpaths), len(paths), len(newaccts), len(accts), "")
|
||||
}
|
||||
t.Fatalf("Error: len(newpaths): %d, len(paths): %d\n len(newaccts): %d, len(accts): %d\n Error= %s", len(newpaths), len(paths), len(newaccts), len(accts), err.Error())
|
||||
}
|
||||
|
||||
if err := helper.Delete(creds.ServerURL); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "secretservice_linux.h"
|
||||
|
||||
const SecretSchema *docker_get_schema(void)
|
||||
@@ -96,3 +97,52 @@ GError *get(char *server, char **username, char **secret) {
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
GError *list(char *** paths, char *** accts, unsigned int *list_l) {
|
||||
GList *items;
|
||||
GError *err = NULL;
|
||||
SecretService *service;
|
||||
SecretSearchFlags flags = SECRET_SEARCH_LOAD_SECRETS | SECRET_SEARCH_ALL | SECRET_SEARCH_UNLOCK;
|
||||
GHashTable *attributes;
|
||||
g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
|
||||
attributes = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
|
||||
service = secret_service_get_sync(SECRET_SERVICE_NONE, NULL, &err);
|
||||
items = secret_service_search_sync(service, NULL, attributes, flags, NULL, &err);
|
||||
int numKeys = g_list_length(items);
|
||||
if (err != NULL) {
|
||||
return err;
|
||||
}
|
||||
*paths = (char **) malloc((int)sizeof(char *)*numKeys);
|
||||
*accts = (char **) malloc((int)sizeof(char *)*numKeys);
|
||||
// items now contains our keys from the gnome keyring
|
||||
// we will now put it in our two lists to return it to go
|
||||
GList *current;
|
||||
int listNumber = 0;
|
||||
for(current = items; current!=NULL; current = current->next) {
|
||||
char *pathTmp = secret_item_get_label(current->data);
|
||||
// you cannot have a key without a label in the gnome keyring
|
||||
char *acctTmp = get_username(current->data);
|
||||
if (acctTmp==NULL) {
|
||||
acctTmp = "account not defined";
|
||||
}
|
||||
char *path = (char *) malloc(strlen(pathTmp));
|
||||
char *acct = (char *) malloc(strlen(acctTmp));
|
||||
path = pathTmp;
|
||||
acct = acctTmp;
|
||||
(*paths)[listNumber] = (char *) malloc(sizeof(char)*(strlen(path)));
|
||||
memcpy((*paths)[listNumber], path, sizeof(char)*(strlen(path)));
|
||||
(*accts)[listNumber] = (char *) malloc(sizeof(char)*(strlen(acct)));
|
||||
memcpy((*accts)[listNumber], acct, sizeof(char)*(strlen(acct)));
|
||||
listNumber = listNumber + 1;
|
||||
}
|
||||
*list_l = numKeys;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void freeListData(char *** data, unsigned int length) {
|
||||
int i;
|
||||
for(i=0; i<length; i++) {
|
||||
free((*data)[i]);
|
||||
}
|
||||
free(*data);
|
||||
}
|
||||
|
||||
@@ -78,3 +78,28 @@ func (h Secretservice) Get(serverURL string) (string, string, error) {
|
||||
}
|
||||
return user, pass, nil
|
||||
}
|
||||
|
||||
func (h Secretservice) List() ([]string, []string, error) {
|
||||
var pathsC **C.char
|
||||
defer C.free(unsafe.Pointer(pathsC))
|
||||
var acctsC **C.char
|
||||
defer C.free(unsafe.Pointer(acctsC))
|
||||
var listLenC C.uint
|
||||
err := C.list(&pathsC, &acctsC, &listLenC)
|
||||
if err != nil {
|
||||
defer C.free(unsafe.Pointer(err))
|
||||
return nil, nil, errors.New("Error from list function in secretservice_linux.c likely due to error in secretservice library")
|
||||
}
|
||||
listLen := int(listLenC)
|
||||
pathTmp := (*[1 << 30]*C.char)(unsafe.Pointer(pathsC))[:listLen:listLen]
|
||||
acctTmp := (*[1 << 30]*C.char)(unsafe.Pointer(acctsC))[:listLen:listLen]
|
||||
paths := make([]string, listLen)
|
||||
accts := make([]string, listLen)
|
||||
for i := 0; i < listLen; i++ {
|
||||
paths[i] = C.GoString(pathTmp[i])
|
||||
accts[i] = C.GoString(acctTmp[i])
|
||||
}
|
||||
C.freeListData(&pathsC, listLenC)
|
||||
C.freeListData(&acctsC, listLenC)
|
||||
return paths, accts, nil
|
||||
}
|
||||
|
||||
@@ -9,3 +9,5 @@ const SecretSchema *docker_get_schema(void) G_GNUC_CONST;
|
||||
GError *add(char *server, char *username, char *secret);
|
||||
GError *delete(char *server);
|
||||
GError *get(char *server, char **username, char **secret);
|
||||
GError *list(char *** paths, char *** accts, unsigned int *list_l);
|
||||
void freeListData(char *** data, unsigned int length);
|
||||
|
||||
@@ -36,6 +36,14 @@ func TestSecretServiceHelper(t *testing.T) {
|
||||
if err := helper.Delete(creds.ServerURL); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
paths, accts, err := helper.List()
|
||||
if err != nil || len(paths) == 0 || len(accts) == 0 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
helper.Add(creds)
|
||||
if newpaths, newaccts, err := helper.List(); (len(newpaths)-len(paths)) != 1 || (len(newaccts)-len(accts)) != 1 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMissingCredentials(t *testing.T) {
|
||||
|
||||
-1
@@ -58,7 +58,6 @@ func nativeToCredential(cred *nativeCREDENTIAL) (result *Credential) {
|
||||
resultAttr.Keyword = utf16PtrToString(attr.Keyword)
|
||||
resultAttr.Value = C.GoBytes(unsafe.Pointer(attr.Value), C.int(attr.ValueSize))
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
+36
-4
@@ -1,6 +1,7 @@
|
||||
package wincred
|
||||
|
||||
import (
|
||||
"C"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
@@ -8,10 +9,11 @@ import (
|
||||
var (
|
||||
modadvapi32 = syscall.NewLazyDLL("advapi32.dll")
|
||||
|
||||
procCredRead = modadvapi32.NewProc("CredReadW")
|
||||
procCredWrite = modadvapi32.NewProc("CredWriteW")
|
||||
procCredDelete = modadvapi32.NewProc("CredDeleteW")
|
||||
procCredFree = modadvapi32.NewProc("CredFree")
|
||||
procCredRead = modadvapi32.NewProc("CredReadW")
|
||||
procCredWrite = modadvapi32.NewProc("CredWriteW")
|
||||
procCredDelete = modadvapi32.NewProc("CredDeleteW")
|
||||
procCredFree = modadvapi32.NewProc("CredFree")
|
||||
procCredEnumerate = modadvapi32.NewProc("CredEnumerateW")
|
||||
)
|
||||
|
||||
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa374788(v=vs.85).aspx
|
||||
@@ -97,3 +99,33 @@ func nativeCredDelete(cred *Credential, typ nativeCRED_TYPE) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa374794(v=vs.85).aspx
|
||||
func nativeCredEnumerate(filter string, all bool) ([]*Credential, error) {
|
||||
var count int
|
||||
var pcreds uintptr
|
||||
var filterPtr uintptr
|
||||
if !all {
|
||||
filterUtf16Ptr, _ := syscall.UTF16PtrFromString(filter)
|
||||
filterPtr = uintptr(unsafe.Pointer(filterUtf16Ptr))
|
||||
} else {
|
||||
filterPtr = 0
|
||||
}
|
||||
ret, _, err := procCredEnumerate.Call(
|
||||
filterPtr,
|
||||
0,
|
||||
uintptr(unsafe.Pointer(&count)),
|
||||
uintptr(unsafe.Pointer(&pcreds)),
|
||||
)
|
||||
if ret == 0 {
|
||||
return nil, err
|
||||
}
|
||||
defer procCredFree.Call(pcreds)
|
||||
pcredsSlice := (*[1 << 30]uintptr)(unsafe.Pointer(pcreds))[:count:count]
|
||||
creds := make([]*Credential, count)
|
||||
for i := range creds {
|
||||
creds[i] = nativeToCredential((*nativeCREDENTIAL)(unsafe.Pointer(pcredsSlice[i])))
|
||||
}
|
||||
|
||||
return creds, nil
|
||||
}
|
||||
|
||||
+5
@@ -67,3 +67,8 @@ func (t *DomainPassword) Delete() (err error) {
|
||||
func (t *DomainPassword) SetPassword(pw string) {
|
||||
t.CredentialBlob = utf16ToByte(syscall.StringToUTF16(pw))
|
||||
}
|
||||
|
||||
// List the contents of the Credentials store
|
||||
func List() ([]*Credential, error) {
|
||||
return nativeCredEnumerate("", true)
|
||||
}
|
||||
|
||||
@@ -37,3 +37,17 @@ func (h Wincred) Get(serverURL string) (string, string, error) {
|
||||
}
|
||||
return g.UserName, string(g.CredentialBlob), nil
|
||||
}
|
||||
|
||||
func (h Wincred) List() ([]string, []string, error) {
|
||||
creds, err := winc.List()
|
||||
paths := make([]string, len(creds))
|
||||
accts := make([]string, len(creds))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
for i := range creds {
|
||||
paths[i] = creds[i].TargetName
|
||||
accts[i] = creds[i].UserName
|
||||
}
|
||||
return paths, accts, nil
|
||||
}
|
||||
|
||||
@@ -12,6 +12,11 @@ func TestWinCredHelper(t *testing.T) {
|
||||
Username: "foobar",
|
||||
Secret: "foobarbaz",
|
||||
}
|
||||
creds1 := &credentials.Credentials{
|
||||
ServerURL: "https://foobar.docker.io:2376/v2",
|
||||
Username: "foobarbaz",
|
||||
Secret: "foobar",
|
||||
}
|
||||
|
||||
helper := Wincred{}
|
||||
if err := helper.Add(creds); err != nil {
|
||||
@@ -31,6 +36,21 @@ func TestWinCredHelper(t *testing.T) {
|
||||
t.Fatalf("expected %s, got %s\n", "foobarbaz", secret)
|
||||
}
|
||||
|
||||
paths, accts, err := helper.List()
|
||||
if err != nil || len(paths) == 0 || len(accts) == 0 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
helper.Add(creds1)
|
||||
defer helper.Delete(creds1.ServerURL)
|
||||
newpaths, newaccts, err := helper.List()
|
||||
if len(newpaths)-len(paths) != 1 || len(newaccts)-len(accts) != 1 {
|
||||
if err == nil {
|
||||
t.Fatalf("Error: len(newpaths): %d, len(paths): %d\n len(newaccts): %d, len(accts): %d\n Error= %s", len(newpaths), len(paths), len(newaccts), len(accts), "")
|
||||
}
|
||||
t.Fatalf("Error: len(newpaths): %d, len(paths): %d\n len(newaccts): %d, len(accts): %d\n Error= %s", len(newpaths), len(paths), len(newaccts), len(accts), err.Error())
|
||||
}
|
||||
|
||||
if err := helper.Delete(creds.ServerURL); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user