forked from jenkins-x/jx
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Handling git credentials via credential helper
fixes jenkins-x#5772
- Loading branch information
1 parent
9345f7f
commit d2c8419
Showing
12 changed files
with
667 additions
and
55 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
package credentialhelper | ||
|
||
import ( | ||
"fmt" | ||
"net/url" | ||
"reflect" | ||
"strings" | ||
|
||
"github.com/jenkins-x/jx/pkg/util" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
// GitCredential represents the different parts of a git credential URL | ||
// See also https://git-scm.com/docs/git-credential | ||
type GitCredential struct { | ||
Protocol string | ||
Host string | ||
Path string | ||
Username string | ||
Password string | ||
} | ||
|
||
// CreateGitCredential creates a CreateGitCredential instance from a slice of strings where each element is a key/value pair | ||
// separated by '='. | ||
func CreateGitCredential(lines []string) (GitCredential, error) { | ||
var credential GitCredential | ||
|
||
if lines == nil { | ||
return credential, errors.New("no data lines provided") | ||
} | ||
|
||
fieldMap, err := util.ExtractKeyValuePairs(lines, "=") | ||
if err != nil { | ||
return credential, errors.Wrap(err, "unable to extract git credential parameters") | ||
} | ||
for key, value := range fieldMap { | ||
v := reflect.ValueOf(&credential).Elem().FieldByName(strings.Title(key)) | ||
if v.IsValid() { | ||
v.SetString(value) | ||
} else { | ||
return GitCredential{}, errors.Errorf("invalid key/value %s/%s", key, value) | ||
} | ||
} | ||
|
||
return credential, nil | ||
} | ||
|
||
// CreateGitCredentialFromURL creates a CreateGitCredential instance from a URL and optional username and password. | ||
func CreateGitCredentialFromURL(gitURL string, username string, password string) (GitCredential, error) { | ||
var credential GitCredential | ||
|
||
if gitURL == "" { | ||
return credential, errors.New("url cannot be empty") | ||
} | ||
|
||
u, err := url.Parse(gitURL) | ||
if err != nil { | ||
return credential, errors.Wrapf(err, "unable to parse URL %s", gitURL) | ||
} | ||
|
||
credential.Protocol = u.Scheme | ||
credential.Host = u.Host | ||
credential.Path = u.Path | ||
if username != "" { | ||
credential.Username = username | ||
} | ||
|
||
if password != "" { | ||
credential.Password = password | ||
} | ||
|
||
return credential, nil | ||
} | ||
|
||
// String returns a string representation of this instance according to the expected format of git credential helpers. | ||
// See also https://git-scm.com/docs/git-credential | ||
func (g *GitCredential) String() string { | ||
answer := "" | ||
|
||
value := reflect.ValueOf(g).Elem() | ||
typeOfT := value.Type() | ||
|
||
for i := 0; i < value.NumField(); i++ { | ||
field := value.Field(i) | ||
answer = answer + fmt.Sprintf("%s=%v\n", strings.ToLower(typeOfT.Field(i).Name), field.Interface()) | ||
} | ||
|
||
answer = answer + "\n" | ||
|
||
return answer | ||
} | ||
|
||
// Clones this GitCredential instance | ||
func (g *GitCredential) Clone() GitCredential { | ||
clone := GitCredential{} | ||
|
||
value := reflect.ValueOf(g).Elem() | ||
typeOfT := value.Type() | ||
for i := 0; i < value.NumField(); i++ { | ||
field := value.Field(i) | ||
value := field.String() | ||
v := reflect.ValueOf(&clone).Elem().FieldByName(typeOfT.Field(i).Name) | ||
v.SetString(value) | ||
} | ||
|
||
return clone | ||
} | ||
|
||
// URL returns a URL from the data of this instance. If not enough information exist an error is returned | ||
func (g *GitCredential) URL() (url.URL, error) { | ||
urlAsString := g.Protocol + "://" + g.Host | ||
if g.Path != "" { | ||
urlAsString = urlAsString + "/" + g.Path | ||
} | ||
u, err := url.Parse(urlAsString) | ||
if err != nil { | ||
return url.URL{}, errors.Wrap(err, "unable to construct URL") | ||
} | ||
|
||
u.User = url.UserPassword(g.Username, g.Password) | ||
return *u, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
package credentialhelper | ||
|
||
import ( | ||
"bufio" | ||
"fmt" | ||
"io" | ||
|
||
"github.com/pkg/errors" | ||
) | ||
|
||
type GitCredentialsHelper struct { | ||
in io.Reader | ||
out io.Writer | ||
knownCredentials []GitCredential | ||
} | ||
|
||
// CreateGitCredentialsHelper creates an instance of a git credential helper. It needs to get passed the handles to read | ||
// the git credential data as well as write the response to. It also gets the list og known credentials. | ||
func CreateGitCredentialsHelper(in io.Reader, out io.Writer, credentials []GitCredential) (*GitCredentialsHelper, error) { | ||
if in == nil { | ||
return nil, errors.New("in parameter cannot be nil") | ||
} | ||
|
||
if out == nil { | ||
return nil, errors.New("out parameter cannot be nil") | ||
} | ||
|
||
if credentials == nil { | ||
return nil, errors.New("credentials parameter cannot be nil") | ||
} | ||
|
||
return &GitCredentialsHelper{ | ||
in: in, | ||
out: out, | ||
knownCredentials: credentials, | ||
}, nil | ||
} | ||
|
||
// Run executes the specified git credential helper operation which must be one of get, store or erase. | ||
// NOTE: Currently only get is implemented. | ||
func (h *GitCredentialsHelper) Run(op string) error { | ||
var err error | ||
|
||
switch op { | ||
case "get": | ||
err = h.Get() | ||
case "store": | ||
// not yet implemented (HF) | ||
fmt.Println("") | ||
case "erase": | ||
// not yet implemented (HF) | ||
fmt.Println("") | ||
default: | ||
err = errors.Errorf("Invalid git credential operation '%s'", op) | ||
} | ||
|
||
return err | ||
} | ||
|
||
func (h *GitCredentialsHelper) Get() error { | ||
var data []string | ||
scanner := bufio.NewScanner(h.in) | ||
for scanner.Scan() { | ||
data = append(data, scanner.Text()) | ||
} | ||
|
||
if scanner.Err() != nil { | ||
return errors.Wrap(scanner.Err(), "unable to read input from stdin") | ||
} | ||
|
||
gitCredential, err := CreateGitCredential(data) | ||
if err != nil { | ||
return errors.Wrap(scanner.Err(), "unable to create GitCredential struct") | ||
} | ||
|
||
answer := h.Fill(gitCredential) | ||
|
||
_, err = fmt.Fprintf(h.out, answer.String()) | ||
if err != nil { | ||
return errors.Wrap(err, "unable to write response to stdin") | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// Fill creates a GitCredential instance based on a git credential helper request which represented by the passed queryCredential instance. | ||
// If there is no auth information available an empty credential instance is returned | ||
func (h *GitCredentialsHelper) Fill(queryCredential GitCredential) GitCredential { | ||
for _, authCredential := range h.knownCredentials { | ||
if queryCredential.Protocol != authCredential.Protocol { | ||
continue | ||
} | ||
|
||
if queryCredential.Host != authCredential.Host { | ||
continue | ||
} | ||
|
||
if queryCredential.Path != authCredential.Path { | ||
continue | ||
} | ||
|
||
answer := authCredential.Clone() | ||
return answer | ||
} | ||
|
||
return GitCredential{} | ||
} |
Oops, something went wrong.