mirror of
https://github.com/docker/docker-credential-helpers.git
synced 2026-06-13 16:01:28 +05:30
Full implementation for OSX ready
Signed-off-by: avaid96 <avaid1996@gmail.com>
This commit is contained in:
@@ -17,6 +17,11 @@ type Credentials struct {
|
|||||||
Secret string
|
Secret string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type KeyData struct{
|
||||||
|
Path string
|
||||||
|
Username string
|
||||||
|
}
|
||||||
|
|
||||||
// Serve initializes the credentials helper and parses the action argument.
|
// Serve initializes the credentials helper and parses the action argument.
|
||||||
// This function is designed to be called from a command line interface.
|
// This function is designed to be called from a command line interface.
|
||||||
// It uses os.Args[1] as the key for the action.
|
// It uses os.Args[1] as the key for the action.
|
||||||
@@ -127,3 +132,26 @@ func Erase(helper Helper, reader io.Reader) error {
|
|||||||
|
|
||||||
return helper.Delete(serverURL)
|
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 {
|
||||||
|
x, y, err := helper.List()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
keyDataList := []KeyData{}
|
||||||
|
for index, _ := range(x) {
|
||||||
|
keyDataObj := KeyData{
|
||||||
|
Path:x[index],
|
||||||
|
Username:y[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
|
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) {
|
func TestStore(t *testing.T) {
|
||||||
serverURL := "https://index.docker.io/v1/"
|
serverURL := "https://index.docker.io/v1/"
|
||||||
creds := &Credentials{
|
creds := &Credentials{
|
||||||
@@ -138,3 +143,16 @@ func TestErase(t *testing.T) {
|
|||||||
t.Fatal("expected error getting missing creds, got empty")
|
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)
|
||||||
|
}
|
||||||
|
if out.Len() == 0 {
|
||||||
|
t.Fatalf("expected output in the writer, got %d", 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,4 +9,7 @@ type Helper interface {
|
|||||||
// Get retrieves credentials from the store.
|
// Get retrieves credentials from the store.
|
||||||
// It returns username and secret as strings.
|
// It returns username and secret as strings.
|
||||||
Get(serverURL string) (string, string, error)
|
Get(serverURL string) (string, string, error)
|
||||||
|
// List returns all the serverURLs of keys in
|
||||||
|
// the OS store as a list of strings
|
||||||
|
List() ([]string, []string, error)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
#include "osxkeychain_darwin.h"
|
#include "osxkeychain_darwin.h"
|
||||||
|
#include <CoreFoundation/CoreFoundation.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
char *get_error(OSStatus status) {
|
char *get_error(OSStatus status) {
|
||||||
char *buf = malloc(128);
|
char *buf = malloc(128);
|
||||||
@@ -96,3 +99,77 @@ char *keychain_delete(struct Server *server) {
|
|||||||
}
|
}
|
||||||
return NULL;
|
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("<unknown>");
|
||||||
|
}
|
||||||
|
char * path = (char *) malloc(CFStringGetLength(pathTmp)+1);
|
||||||
|
path = CFStringToCharArr(pathTmp);
|
||||||
|
path[strlen(path)] = '\0';
|
||||||
|
char * acct = (char *) malloc(CFStringGetLength(acctTmp)+1); //<- problem line in 38th iteration
|
||||||
|
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);
|
||||||
|
}
|
||||||
@@ -14,7 +14,6 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/docker/docker-credential-helpers/credentials"
|
"github.com/docker/docker-credential-helpers/credentials"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -83,7 +82,6 @@ func (h Osxkeychain) Get(serverURL string) (string, string, error) {
|
|||||||
if errMsg != nil {
|
if errMsg != nil {
|
||||||
defer C.free(unsafe.Pointer(errMsg))
|
defer C.free(unsafe.Pointer(errMsg))
|
||||||
goMsg := C.GoString(errMsg)
|
goMsg := C.GoString(errMsg)
|
||||||
|
|
||||||
if goMsg == errCredentialsNotFound {
|
if goMsg == errCredentialsNotFound {
|
||||||
return "", "", credentials.NewErrCredentialsNotFound()
|
return "", "", credentials.NewErrCredentialsNotFound()
|
||||||
}
|
}
|
||||||
@@ -96,6 +94,42 @@ func (h Osxkeychain) Get(serverURL string) (string, string, error) {
|
|||||||
return user, pass, nil
|
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]
|
||||||
|
//still need to free all the memory we allocated in the c file
|
||||||
|
//do it here >>
|
||||||
|
C.freeListData(&pathsC, listLenC)
|
||||||
|
return paths, accts, nil
|
||||||
|
}
|
||||||
|
|
||||||
func splitServer(serverURL string) (*C.struct_Server, error) {
|
func splitServer(serverURL string) (*C.struct_Server, error) {
|
||||||
u, err := url.Parse(serverURL)
|
u, err := url.Parse(serverURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -130,3 +164,4 @@ func freeServer(s *C.struct_Server) {
|
|||||||
C.free(unsafe.Pointer(s.host))
|
C.free(unsafe.Pointer(s.host))
|
||||||
C.free(unsafe.Pointer(s.path))
|
C.free(unsafe.Pointer(s.path))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,3 +10,5 @@ struct Server {
|
|||||||
char *keychain_add(struct Server *server, char *username, char *secret);
|
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_get(struct Server *server, unsigned int *username_l, char **username, unsigned int *secret_l, char **secret);
|
||||||
char *keychain_delete(struct Server *server);
|
char *keychain_delete(struct Server *server);
|
||||||
|
char *keychain_list(char *** data, char *** accts, unsigned int *list_l);
|
||||||
|
void freeListData(char *** data, unsigned int length);
|
||||||
@@ -2,7 +2,6 @@ package osxkeychain
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/docker-credential-helpers/credentials"
|
"github.com/docker/docker-credential-helpers/credentials"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -34,6 +33,11 @@ func TestOSXKeychainHelper(t *testing.T) {
|
|||||||
if err := helper.Delete(creds.ServerURL); err != nil {
|
if err := helper.Delete(creds.ServerURL); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_, _, err = helper.List();
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMissingCredentials(t *testing.T) {
|
func TestMissingCredentials(t *testing.T) {
|
||||||
|
|||||||
Reference in New Issue
Block a user