Skip to content

Commit

Permalink
Merge pull request #12 from sonatype-nexus-community/feat/handle-unsu…
Browse files Browse the repository at this point in the history
…pported-branch-names

feat: only create SCM Configuration when Default Branch and Repository URL meet IQ requirements
  • Loading branch information
madpah authored Dec 10, 2024
2 parents 50cfc98 + 8690a51 commit e89ff43
Show file tree
Hide file tree
Showing 3 changed files with 180 additions and 32 deletions.
80 changes: 49 additions & 31 deletions iq/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,12 +156,14 @@ func (s *NxiqServer) ApplyOrgContents(orgContent scm.OrgContents, rootOrganizati
func (s *NxiqServer) createAppsInOrg(org *sonatypeiq.ApiOrganizationDTO, apps []scm.Application) error {
if len(apps) > 0 {
for _, a := range apps {
app, err := s.CreateApplication(a, *org.Id)
app, scm, err := s.CreateApplication(a, *org.Id)
if err != nil {
return err
}
log.Debug(fmt.Sprintf("Created Application %s - %s", a.SafeName(), *app.Id))
s.scheduleSourceStageScan(app, a.DefaultBranch)
if scm != nil {
s.scheduleSourceStageScan(app, a.DefaultBranch)
}
}
}
return nil
Expand Down Expand Up @@ -321,16 +323,52 @@ func (s *NxiqServer) UpdateOrganizationScmConfiguration(org *sonatypeiq.ApiOrgan
return nil
}

func (s *NxiqServer) CreateApplication(app scm.Application, parentOrgId string) (*sonatypeiq.ApiApplicationDTO, error) {
func (s *NxiqServer) CreateApplication(app scm.Application, parentOrgId string) (*sonatypeiq.ApiApplicationDTO, *sonatypeiq.ApiSourceControlDTO, error) {
existingApp, err := s.ApplicationExists(app, parentOrgId)
if err != nil {
log.Debug(fmt.Sprintf("Failed to determine if Application %s already exists", app.Name))
return nil, err
return nil, nil, err
}

var scmDto *sonatypeiq.ApiSourceControlDTO
if existingApp != nil {
// Update SCM Configuration
_, r, err := s.apiClient.SourceControlAPI.UpdateSourceControl(*s.apiContext, "application", *existingApp.Id).ApiSourceControlDTO(sonatypeiq.ApiSourceControlDTO{
if app.IsRepositoryUrlPermitted() && app.IsBranchNamePermitted() {
scmDto, r, err := s.apiClient.SourceControlAPI.UpdateSourceControl(*s.apiContext, "application", *existingApp.Id).ApiSourceControlDTO(sonatypeiq.ApiSourceControlDTO{
RepositoryUrl: &app.RepositoryUrl,
BaseBranch: app.DefaultBranch,
EnablePullRequests: nil,
RemediationPullRequestsEnabled: nil,
PullRequestCommentingEnabled: nil,
SourceControlEvaluationsEnabled: nil,
SshEnabled: nil,
}).Execute()
if err != nil {
fmt.Fprintf(os.Stderr, "Error when calling `SourceControlAPI.UpdateSourceControl``: %v\n", err)
fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r)
return nil, nil, err
}
return existingApp, scmDto, nil
} else {
log.Warn(fmt.Sprintf("Application %s has an unsupported Default Branch or Repository URL '%s' and will not have SCM configuration saved into Sonatype", app.Name, app.RepositoryUrl))
}
}

createdApp, err := s.createApplication(app, parentOrgId)
if err != nil {
return nil, nil, err
}
log.Debug(fmt.Sprintf("Created App: %s (%s)", *createdApp.Name, *createdApp.Id))

// Set SCM Configuration
if app.IsRepositoryUrlPermitted() && app.IsBranchNamePermitted() {
log.Debug(
fmt.Sprintf(
"APP Source Control. URL: '%s' %v, Branch: '%s' %v",
app.RepositoryUrl, app.IsRepositoryUrlPermitted(), *app.DefaultBranch, app.IsBranchNamePermitted(),
),
)
scmDto, r, err := s.apiClient.SourceControlAPI.AddSourceControl(*s.apiContext, "application", *createdApp.Id).ApiSourceControlDTO(sonatypeiq.ApiSourceControlDTO{
RepositoryUrl: &app.RepositoryUrl,
BaseBranch: app.DefaultBranch,
EnablePullRequests: nil,
Expand All @@ -340,36 +378,16 @@ func (s *NxiqServer) CreateApplication(app scm.Application, parentOrgId string)
SshEnabled: nil,
}).Execute()
if err != nil {
fmt.Fprintf(os.Stderr, "Error when calling `SourceControlAPI.UpdateSourceControl``: %v\n", err)
fmt.Fprintf(os.Stderr, "Error when calling `SourceControlAPI.AddSourceControl``: %v\n", err)
fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r)
return nil, err
return nil, nil, err
}
return existingApp, nil
return createdApp, scmDto, nil
} else {
log.Warn(fmt.Sprintf("Application %s has an unsupported Default Branch or Repository URL '%s' and will not have SCM configuration saved into Sonatype", app.Name, app.RepositoryUrl))
}

createdApp, err := s.createApplication(app, parentOrgId)
if err != nil {
return nil, err
}
log.Debug(fmt.Sprintf("Created App: %v", createdApp))

// Set SCM Configuration
_, r, err := s.apiClient.SourceControlAPI.AddSourceControl(*s.apiContext, "application", *createdApp.Id).ApiSourceControlDTO(sonatypeiq.ApiSourceControlDTO{
RepositoryUrl: &app.RepositoryUrl,
BaseBranch: app.DefaultBranch,
EnablePullRequests: nil,
RemediationPullRequestsEnabled: nil,
PullRequestCommentingEnabled: nil,
SourceControlEvaluationsEnabled: nil,
SshEnabled: nil,
}).Execute()
if err != nil {
fmt.Fprintf(os.Stderr, "Error when calling `SourceControlAPI.AddSourceControl``: %v\n", err)
fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r)
return nil, err
}

return createdApp, nil
return createdApp, scmDto, nil
}

func (s *NxiqServer) ApplicationExists(app scm.Application, parentOrgId string) (*sonatypeiq.ApiApplicationDTO, error) {
Expand Down
35 changes: 34 additions & 1 deletion scm/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@ package scm

import (
"fmt"
"net/url"
"regexp"
"strings"

log "github.com/sirupsen/logrus"
)

const (
Expand All @@ -28,7 +31,10 @@ const (
SCM_TYPE_AZURE = "azure"
)

var MULTIPLE_SPACES = regexp.MustCompile(`\s(\s+)`)
var (
INVALID_BRANCH_NAME = regexp.MustCompile(`^\.|([;$!*&|\(\)\[\]<>#?~%'])|[\./]$`)
MULTIPLE_SPACES = regexp.MustCompile(`\s(\s+)`)
)

type ScmConfiguration struct {
Type string
Expand Down Expand Up @@ -60,6 +66,33 @@ func (a *Application) SafeName() string {
return safeName(a.Name)
}

func (a *Application) IsBranchNamePermitted() bool {
if a.DefaultBranch != nil {
return safeBranchName(*a.DefaultBranch)
}
return false
}

func (a *Application) IsRepositoryUrlPermitted() bool {
return safeRepositoryUrl(a.RepositoryUrl)
}

func safeBranchName(in string) bool {
if strings.TrimSpace(in) == "" {
return false
}
return !INVALID_BRANCH_NAME.MatchString(in)
}

func safeRepositoryUrl(in string) bool {
decoded, err := url.QueryUnescape(in)
if err != nil {
log.Warn(fmt.Sprintf("Failed to URL decode Repository URL: %s", in))
return false
}
return !INVALID_BRANCH_NAME.MatchString(decoded)
}

type Organization struct {
Name string
ScmProvider string
Expand Down
97 changes: 97 additions & 0 deletions scm/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,103 @@ import (
"github.com/stretchr/testify/assert"
)

func TestSafeBranchNameNil(t *testing.T) {
var nil string
assert.Equal(t, false, safeBranchName(nil))
}

func TestSafeBranchName(t *testing.T) {

cases := []struct {
input string
permitted bool
}{
{
input: "main",
permitted: true,
},
{
input: "master",
permitted: true,
},
{
input: "with(bracket",
permitted: false,
},
{
input: "with&mpersand",
permitted: false,
},
{
input: "ma?in",
permitted: false,
},
{
input: "pi|pe",
permitted: false,
},
{
input: "give;injection",
permitted: false,
},
{
input: "give~injection",
permitted: false,
},
{
input: "give%injection",
permitted: false,
},
{
input: "give'injection",
permitted: false,
},
{
input: ".start-period",
permitted: false,
},
{
input: "end-period.",
permitted: false,
},
{
input: "end-slash/",
permitted: false,
},
}

for i, tc := range cases {
t.Run(fmt.Sprintf("TestSafeBranchName-%d-%s", i, tc.input), func(t *testing.T) {
assert.Equal(t, tc.permitted, safeBranchName(tc.input))
})
}
}

func TestSafeRepositoryUrl(t *testing.T) {

cases := []struct {
input string
permitted bool
}{
{
input: "https://[email protected]/REDACTED/Scan-Test-1/_git/main",
permitted: true,
}, {
input: "https://[email protected]/REDACTED/Scan-Test-1/_git/Craz%28%29y%20Repo",
permitted: false,
}, {
input: "https://dev.azure.com/PHorton0655/Scan-Test-1/_git/Craz%28%29y%20Repo",
permitted: false,
},
}

for i, tc := range cases {
t.Run(fmt.Sprintf("TestSafeRepositoryUrl-%d-%s", i, tc.input), func(t *testing.T) {
assert.Equal(t, tc.permitted, safeRepositoryUrl(tc.input))
})
}
}

func TestScmSafeName(t *testing.T) {

cases := []struct {
Expand Down

0 comments on commit e89ff43

Please sign in to comment.