Skip to content
This repository was archived by the owner on Nov 28, 2022. It is now read-only.

feat: 2647.4 add secure template repos via PAT #453

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -327,8 +327,9 @@ Subcommands:</br>
> --url - URL to template repository index.json
> --name - Custom name for template repository
> --description - Custom description for template repository
> --username - GitHub username (required if accessing the provided URL requires GitHub authentication)
> --password - GitHub password (required if accessing the provided URL requires GitHub authentication)
> --username - GitHub username (required if accessing the provided URL requires GitHub authentication and you do not provide --personalAccessToken)
> --password - GitHub password (required if accessing the provided URL requires GitHub authentication and you do not provide --personalAccessToken)
> --personalAccessToken - GitHub personal access token (required if accessing the provided URL requires GitHub authentication and you do not provide --username and --password)

### version

Expand Down
59 changes: 54 additions & 5 deletions cmd/cli/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,9 @@ func testCreateProjectFromTemplate(t *testing.T) {
"--username="+test.GHEUsername,
"--password=badpassword",
)
out, err := cmd.Output()
out, err := cmd.CombinedOutput()
assert.NotNil(t, err)
assert.Equal(t, "", string(out))
assert.Contains(t, string(out), "401 Unauthorized")
})
}

Expand Down Expand Up @@ -193,7 +193,7 @@ func testSuccessfulAddAndDeleteTemplateRepos(t *testing.T) {
assert.Nil(t, removeErr)
assert.NotContains(t, string(removeOut), test.PublicGHDevfileURL)
})
t.Run("cwctl templates repos add --url <GHEDevfile>"+
t.Run("cwctl templates repos add --url <GHEDevfile> --username --password"+
"\n then cwctl templates repos remove --url", func(t *testing.T) {
if !test.UsingOwnGHECredentials {
t.Skip("skipping this test because you haven't set GitHub credentials needed for this test")
Expand All @@ -208,6 +208,27 @@ func testSuccessfulAddAndDeleteTemplateRepos(t *testing.T) {
assert.Nil(t, err)
assert.Contains(t, string(out), test.GHEDevfileURL)

removeCmd := exec.Command(cwctl, "templates", "repos", "remove",
"--url="+test.GHEDevfileURL,
)
removeOut, removeErr := removeCmd.Output()
assert.Nil(t, removeErr)
assert.NotContains(t, string(removeOut), test.GHEDevfileURL)
})
t.Run("cwctl templates repos add --url <GHEDevfile> --personalAccessToken"+
"\n then cwctl templates repos remove --url", func(t *testing.T) {
if !test.UsingOwnGHECredentials {
t.Skip("skipping this test because you haven't set GitHub credentials needed for this test")
}

cmd := exec.Command(cwctl, "templates", "repos", "add",
"--url="+test.GHEDevfileURL,
"--personalAccessToken="+test.GHEPersonalAccessToken,
)
out, err := cmd.Output()
assert.Nil(t, err)
assert.Contains(t, string(out), test.GHEDevfileURL)

removeCmd := exec.Command(cwctl, "templates", "repos", "remove",
"--url="+test.GHEDevfileURL,
)
Expand All @@ -222,8 +243,36 @@ func testFailToAddTemplateRepo(t *testing.T) {
cmd := exec.Command(cwctl, "templates", "repos", "add",
"--url=https://raw.githubusercontent.com/kabanero-io/codewind-templates/aad4bafc14e1a295fb8e462c20fe8627248609a3/devfiles/NOT_INDEX_JSON",
)
out, err := cmd.Output()
out, err := cmd.CombinedOutput()
assert.Nil(t, err)
assert.Contains(t, string(out), "does not point to a JSON file of the correct form")
})
t.Run("cwctl templates repos add --url <GHEDevfile> --personalAccessToken --username", func(t *testing.T) {
cmd := exec.Command(cwctl, "templates", "repos", "add",
"--url="+test.GHEDevfileURL,
"--personalAccessToken=validPersonalAccessToken",
"--username=validUsername",
)
out, err := cmd.CombinedOutput()
assert.Nil(t, err)
assert.Contains(t, string(out), "received credentials for multiple authentication methods")
})
t.Run("cwctl templates repos add --url <GHEDevfile> --username", func(t *testing.T) {
cmd := exec.Command(cwctl, "templates", "repos", "add",
"--url="+test.GHEDevfileURL,
"--username=validUsername",
)
out, err := cmd.CombinedOutput()
assert.Nil(t, err)
assert.Contains(t, string(out), "received username but no password")
})
t.Run("cwctl templates repos add --url <GHEDevfile> --password", func(t *testing.T) {
cmd := exec.Command(cwctl, "templates", "repos", "add",
"--url="+test.GHEDevfileURL,
"--password=validPassword",
)
out, err := cmd.CombinedOutput()
assert.Nil(t, err)
assert.Equal(t, string(out), "")
assert.Contains(t, string(out), "received password but no username")
})
}
5 changes: 5 additions & 0 deletions pkg/actions/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,11 @@ func Commands() {
Usage: "GitHub password",
Required: false,
},
cli.StringFlag{
Name: "personalAccessToken",
Usage: "GitHub personal access token",
Required: false,
},
},
Action: func(c *cli.Context) error {
AddTemplateRepo(c)
Expand Down
10 changes: 7 additions & 3 deletions pkg/actions/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,15 @@ func AddTemplateRepo(c *cli.Context) {
name := c.String("name")
username := c.String("username")
password := c.String("password")
personalAccessToken := c.String("personalAccessToken")

gitCredentials := utils.GitCredentials{
Username: username,
Password: password,
gitCredentials, err := utils.ExtractGitCredentials(username, password, personalAccessToken)
if err != nil {
templateErr := &TemplateError{errOpAddRepo, err, err.Error()}
HandleTemplateError(templateErr)
return
}

conID := strings.TrimSpace(strings.ToLower(c.String("conid")))
repos, err := apiroutes.AddTemplateRepo(conID, url, desc, name, gitCredentials)
if err != nil {
Expand Down
10 changes: 5 additions & 5 deletions pkg/apiroutes/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ type (

// RepoCreationOptions is the request body for PFE's POST /templates/repositories API
RepoCreationOptions struct {
URL string `json:"url"`
Description string `json:"description"`
Name string `json:"name"`
GitCredentials utils.GitCredentials `json:"gitCredentials"`
URL string `json:"url,omitempty"`
Description string `json:"description,omitempty"`
Name string `json:"name,omitempty"`
GitCredentials *utils.GitCredentials `json:"gitCredentials,omitempty"`
}

// RepoOperation represents a requested operation on a template repository.
Expand Down Expand Up @@ -187,7 +187,7 @@ func GetTemplateRepos(conID string) ([]utils.TemplateRepo, error) {

// AddTemplateRepo adds a template repo to PFE and
// returns the new list of existing repos
func AddTemplateRepo(conID, URL, description, name string, gitCredentials utils.GitCredentials) ([]utils.TemplateRepo, error) {
func AddTemplateRepo(conID, URL, description, name string, gitCredentials *utils.GitCredentials) ([]utils.TemplateRepo, error) {
if _, err := url.ParseRequestURI(URL); err != nil {
return nil, fmt.Errorf("Error: '%s' is not a valid URL", URL)
}
Expand Down
20 changes: 13 additions & 7 deletions pkg/apiroutes/templates_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ func TestFailuresAddTemplateRepo(t *testing.T) {
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
got, err := AddTemplateRepo("local", test.inURL, test.inDescription, "template-name", utils.GitCredentials{})
got, err := AddTemplateRepo("local", test.inURL, test.inDescription, "template-name", nil)
assert.IsType(t, test.wantedType, got, "got: %v", got)
assert.Equal(t, test.wantedErr, err)
})
Expand Down Expand Up @@ -206,20 +206,26 @@ func TestSuccessfulAddAndDeleteTemplateRepos(t *testing.T) {
tests := map[string]struct {
skip bool
inURL string
inGitCredentials utils.GitCredentials
inGitCredentials *utils.GitCredentials
}{
"PublicGHDevFileURL": {
inURL: cwTest.PublicGHDevfileURL,
inGitCredentials: utils.GitCredentials{},
"public GH devfile URL": {
inURL: cwTest.PublicGHDevfileURL,
},
"GHEDevfileURL": {
"GHE devfile URL with GHE basic credentials": {
skip: !cwTest.UsingOwnGHECredentials,
inURL: cwTest.GHEDevfileURL,
inGitCredentials: utils.GitCredentials{
inGitCredentials: &utils.GitCredentials{
Username: test.GHEUsername,
Password: test.GHEPassword,
},
},
"GHE devfile URL with GHE personal access token": {
skip: !cwTest.UsingOwnGHECredentials,
inURL: cwTest.GHEDevfileURL,
inGitCredentials: &utils.GitCredentials{
PersonalAccessToken: test.GHEPersonalAccessToken,
},
},
}
for name, test := range tests {
if test.skip {
Expand Down
3 changes: 3 additions & 0 deletions pkg/test/globals.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,8 @@ const GHEUsername = "INSERT YOUR OWN: e.g. [email protected]"
// GHEPassword is a password that passes the auth required to access a GHERepoURL
const GHEPassword = "INSERT YOUR OWN: e.g. 1234kljfdsjfaleru29348spodkfj445"

// GHEPersonalAccessToken is a personal access token that passes the auth required to access a GHERepoURL
const GHEPersonalAccessToken = "INSERT YOUR OWN"

// UsingOwnGHECredentials should be set to true if you want to run tests using the credentials above
const UsingOwnGHECredentials = false
5 changes: 3 additions & 2 deletions pkg/utils/download.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ import (
type (
// GitCredentials : credentials to access GitHub or GitHubEnterprise
GitCredentials struct {
Username string `json:"username"`
Password string `json:"password"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
PersonalAccessToken string `json:"personalAccessToken,omitempty"`
}
)

Expand Down
27 changes: 27 additions & 0 deletions pkg/utils/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

package utils

import "fmt"

type (
// TemplateRepo represents a template repository.
TemplateRepo struct {
Expand Down Expand Up @@ -102,3 +104,28 @@ func OnDeleteTemplateRepo(extensions []Extension, url string, repos []TemplateRe
}
}
}

// ExtractGitCredentials extracts and formats git credentials from the provided arguments
func ExtractGitCredentials(username, password, personalAccessToken string) (*GitCredentials, error) {
if personalAccessToken != "" && (username != "" || password != "") {
return nil, fmt.Errorf("received credentials for multiple authentication methods")
}
if username != "" && password == "" {
return nil, fmt.Errorf("received username but no password")
}
if password != "" && username == "" {
return nil, fmt.Errorf("received password but no username")
}
if username != "" && password != "" {
return &GitCredentials{
Username: username,
Password: password,
}, nil
}
if personalAccessToken != "" {
return &GitCredentials{
PersonalAccessToken: personalAccessToken,
}, nil
}
return nil, nil
}