From b3f48d7021a73edb28fb8dbf32459e9c38b2f3a8 Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Tue, 10 Dec 2024 15:20:38 +0000 Subject: [PATCH 1/6] feat: filter out setting SCM configuration where branch name is unsupported Signed-off-by: Paul Horton --- iq/server.go | 58 +++++++++++++++++++++++++++-------------------- scm/types.go | 13 ++++++++++- scm/types_test.go | 31 +++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 26 deletions(-) diff --git a/iq/server.go b/iq/server.go index f60ec2b..6375dfe 100644 --- a/iq/server.go +++ b/iq/server.go @@ -330,7 +330,36 @@ func (s *NxiqServer) CreateApplication(app scm.Application, parentOrgId string) if existingApp != nil { // Update SCM Configuration - _, r, err := s.apiClient.SourceControlAPI.UpdateSourceControl(*s.apiContext, "application", *existingApp.Id).ApiSourceControlDTO(sonatypeiq.ApiSourceControlDTO{ + if app.IsBranchNamePermitted() { + _, 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, err + } + return existingApp, nil + } else { + log.Warn(fmt.Sprintf("Application %s has an unsupported Default Branch '%s' and will not have SCM configuration saved into Sonatype", app.Name, *app.DefaultBranch)) + } + } + + createdApp, err := s.createApplication(app, parentOrgId) + if err != nil { + return nil, err + } + log.Debug(fmt.Sprintf("Created App: %v", createdApp)) + + // Set SCM Configuration + if app.IsBranchNamePermitted() { + _, r, err := s.apiClient.SourceControlAPI.AddSourceControl(*s.apiContext, "application", *createdApp.Id).ApiSourceControlDTO(sonatypeiq.ApiSourceControlDTO{ RepositoryUrl: &app.RepositoryUrl, BaseBranch: app.DefaultBranch, EnablePullRequests: nil, @@ -340,33 +369,12 @@ 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 existingApp, nil - } - - 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 + } else { + log.Warn(fmt.Sprintf("Application %s has an unsupported Default Branch '%s' and will not have SCM configuration saved into Sonatype", app.Name, *app.DefaultBranch)) } return createdApp, nil diff --git a/scm/types.go b/scm/types.go index 8d4f76b..814413a 100644 --- a/scm/types.go +++ b/scm/types.go @@ -28,7 +28,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 @@ -60,6 +63,14 @@ func (a *Application) SafeName() string { return safeName(a.Name) } +func (a *Application) IsBranchNamePermitted() bool { + return safeBranchName(*a.DefaultBranch) +} + +func safeBranchName(in string) bool { + return !INVALID_BRANCH_NAME.MatchString(in) +} + type Organization struct { Name string ScmProvider string diff --git a/scm/types_test.go b/scm/types_test.go index 55b0923..1b7c8c2 100644 --- a/scm/types_test.go +++ b/scm/types_test.go @@ -23,6 +23,37 @@ import ( "github.com/stretchr/testify/assert" ) +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, + }, + } + + 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 TestScmSafeName(t *testing.T) { cases := []struct { From 9c4d28ed5ba19722c91b16c898639b1cef9b6085 Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Tue, 10 Dec 2024 16:25:24 +0000 Subject: [PATCH 2/6] do not request Source Evaluation when no SCM Configuration Signed-off-by: Paul Horton --- iq/server.go | 42 ++++++++++++++++++++++++++---------------- scm/types.go | 23 +++++++++++++++++++++-- scm/types_test.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 18 deletions(-) diff --git a/iq/server.go b/iq/server.go index 6375dfe..209247a 100644 --- a/iq/server.go +++ b/iq/server.go @@ -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 @@ -321,17 +323,18 @@ 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 - if app.IsBranchNamePermitted() { - _, 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, @@ -343,23 +346,29 @@ func (s *NxiqServer) CreateApplication(app scm.Application, parentOrgId string) 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, err + return nil, nil, err } - return existingApp, nil + return existingApp, scmDto, nil } else { - log.Warn(fmt.Sprintf("Application %s has an unsupported Default Branch '%s' and will not have SCM configuration saved into Sonatype", app.Name, *app.DefaultBranch)) + 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 + return nil, nil, err } - log.Debug(fmt.Sprintf("Created App: %v", createdApp)) + log.Debug(fmt.Sprintf("Created App: %s (%s)", *createdApp.Name, *createdApp.Id)) // Set SCM Configuration - if app.IsBranchNamePermitted() { - _, r, err := s.apiClient.SourceControlAPI.AddSourceControl(*s.apiContext, "application", *createdApp.Id).ApiSourceControlDTO(sonatypeiq.ApiSourceControlDTO{ + 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, @@ -371,13 +380,14 @@ func (s *NxiqServer) CreateApplication(app scm.Application, parentOrgId string) 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 nil, nil, err } + return createdApp, scmDto, nil } else { - log.Warn(fmt.Sprintf("Application %s has an unsupported Default Branch '%s' and will not have SCM configuration saved into Sonatype", app.Name, *app.DefaultBranch)) + 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)) } - return createdApp, nil + return createdApp, scmDto, nil } func (s *NxiqServer) ApplicationExists(app scm.Application, parentOrgId string) (*sonatypeiq.ApiApplicationDTO, error) { diff --git a/scm/types.go b/scm/types.go index 814413a..70aa015 100644 --- a/scm/types.go +++ b/scm/types.go @@ -18,8 +18,11 @@ package scm import ( "fmt" + "net/url" "regexp" "strings" + + log "github.com/sirupsen/logrus" ) const ( @@ -29,7 +32,7 @@ const ( ) var ( - INVALID_BRANCH_NAME = regexp.MustCompile(`([;$!*&\(\)\[\]<>])`) + INVALID_BRANCH_NAME = regexp.MustCompile(`([;$!*&|\(\)\[\]<>#?])`) MULTIPLE_SPACES = regexp.MustCompile(`\s(\s+)`) ) @@ -64,13 +67,29 @@ func (a *Application) SafeName() string { } func (a *Application) IsBranchNamePermitted() bool { - return safeBranchName(*a.DefaultBranch) + if a.DefaultBranch != nil { + return safeBranchName(*a.DefaultBranch) + } + return false +} + +func (a *Application) IsRepositoryUrlPermitted() bool { + return safeRepositoryUrl(a.RepositoryUrl) } func safeBranchName(in string) bool { 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 diff --git a/scm/types_test.go b/scm/types_test.go index 1b7c8c2..a7da5c4 100644 --- a/scm/types_test.go +++ b/scm/types_test.go @@ -23,6 +23,11 @@ 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 { @@ -45,6 +50,18 @@ func TestSafeBranchName(t *testing.T) { input: "with&mpersand", permitted: false, }, + { + input: "ma?in", + permitted: false, + }, + { + input: "pi|pe", + permitted: false, + }, + { + input: "give;injection", + permitted: false, + }, } for i, tc := range cases { @@ -54,6 +71,31 @@ func TestSafeBranchName(t *testing.T) { } } +func TestSafeRepositoryUrl(t *testing.T) { + + cases := []struct { + input string + permitted bool + }{ + { + input: "https://REDACTED@dev.azure.com/REDACTED/Scan-Test-1/_git/main", + permitted: true, + }, { + input: "https://REDACTED@dev.azure.com/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 { From da58f2726765eca85cb5109c1ab3acd736738b74 Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Tue, 10 Dec 2024 16:43:43 +0000 Subject: [PATCH 3/6] ban additional characters from default branch Signed-off-by: Paul Horton --- scm/types.go | 2 +- scm/types_test.go | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/scm/types.go b/scm/types.go index 70aa015..09237d4 100644 --- a/scm/types.go +++ b/scm/types.go @@ -32,7 +32,7 @@ const ( ) var ( - INVALID_BRANCH_NAME = regexp.MustCompile(`([;$!*&|\(\)\[\]<>#?])`) + INVALID_BRANCH_NAME = regexp.MustCompile(`([;$!*&|\(\)\[\]<>#?~%'])`) MULTIPLE_SPACES = regexp.MustCompile(`\s(\s+)`) ) diff --git a/scm/types_test.go b/scm/types_test.go index a7da5c4..2ff9fe5 100644 --- a/scm/types_test.go +++ b/scm/types_test.go @@ -62,6 +62,18 @@ func TestSafeBranchName(t *testing.T) { input: "give;injection", permitted: false, }, + { + input: "give~injection", + permitted: false, + }, + { + input: "give%injection", + permitted: false, + }, + { + input: "give'injection", + permitted: false, + }, } for i, tc := range cases { From 5e49c262fd63f8994a039380162e799e50e6fa24 Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Tue, 10 Dec 2024 16:47:59 +0000 Subject: [PATCH 4/6] ban scm configuration where default branch begins or ends in period Signed-off-by: Paul Horton --- scm/types.go | 2 +- scm/types_test.go | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/scm/types.go b/scm/types.go index 09237d4..5fd4601 100644 --- a/scm/types.go +++ b/scm/types.go @@ -32,7 +32,7 @@ const ( ) var ( - INVALID_BRANCH_NAME = regexp.MustCompile(`([;$!*&|\(\)\[\]<>#?~%'])`) + INVALID_BRANCH_NAME = regexp.MustCompile(`^\.|([;$!*&|\(\)\[\]<>#?~%'])|\.$`) MULTIPLE_SPACES = regexp.MustCompile(`\s(\s+)`) ) diff --git a/scm/types_test.go b/scm/types_test.go index 2ff9fe5..080d22c 100644 --- a/scm/types_test.go +++ b/scm/types_test.go @@ -74,6 +74,14 @@ func TestSafeBranchName(t *testing.T) { input: "give'injection", permitted: false, }, + { + input: ".start-period", + permitted: false, + }, + { + input: "end-period.", + permitted: false, + }, } for i, tc := range cases { From aab4f8f731cde1f2ad8466f0d7af37326dae7f86 Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Tue, 10 Dec 2024 16:49:06 +0000 Subject: [PATCH 5/6] ban default branch ending in slash Signed-off-by: Paul Horton --- scm/types.go | 2 +- scm/types_test.go | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/scm/types.go b/scm/types.go index 5fd4601..839c75d 100644 --- a/scm/types.go +++ b/scm/types.go @@ -32,7 +32,7 @@ const ( ) var ( - INVALID_BRANCH_NAME = regexp.MustCompile(`^\.|([;$!*&|\(\)\[\]<>#?~%'])|\.$`) + INVALID_BRANCH_NAME = regexp.MustCompile(`^\.|([;$!*&|\(\)\[\]<>#?~%'])|[\./]$`) MULTIPLE_SPACES = regexp.MustCompile(`\s(\s+)`) ) diff --git a/scm/types_test.go b/scm/types_test.go index 080d22c..7b292c8 100644 --- a/scm/types_test.go +++ b/scm/types_test.go @@ -82,6 +82,10 @@ func TestSafeBranchName(t *testing.T) { input: "end-period.", permitted: false, }, + { + input: "end-slash/", + permitted: false, + }, } for i, tc := range cases { From 8690a5111571e6e0985118c693c6c93ccbf74bfc Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Tue, 10 Dec 2024 16:52:26 +0000 Subject: [PATCH 6/6] fixed testing empty branch name Signed-off-by: Paul Horton --- scm/types.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scm/types.go b/scm/types.go index 839c75d..9674682 100644 --- a/scm/types.go +++ b/scm/types.go @@ -78,6 +78,9 @@ func (a *Application) IsRepositoryUrlPermitted() bool { } func safeBranchName(in string) bool { + if strings.TrimSpace(in) == "" { + return false + } return !INVALID_BRANCH_NAME.MatchString(in) }