Skip to content
1 change: 1 addition & 0 deletions internal/commands/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ func NewAstCLI(
featureFlagsWrapper,
containerResolverWrapper,
realTimeWrapper,
tenantWrapper,
)
projectCmd := NewProjectCommand(applicationsWrapper, projectsWrapper, groupsWrapper, accessManagementWrapper, featureFlagsWrapper)
dastEnvironmentsCmd := dast.NewDastEnvironmentsCommand(dastEnvironmentsWrapper)
Expand Down
11 changes: 10 additions & 1 deletion internal/commands/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ func NewScanCommand(
featureFlagsWrapper wrappers.FeatureFlagsWrapper,
containerResolverWrapper wrappers.ContainerResolverWrapper,
realtimeScannerWrapper wrappers.RealtimeScannerWrapper,
tenantWrapper wrappers.TenantConfigurationWrapper,
) *cobra.Command {
scanCmd := &cobra.Command{
Use: "scan",
Expand Down Expand Up @@ -217,6 +218,7 @@ func NewScanCommand(
accessManagementWrapper,
applicationsWrapper,
featureFlagsWrapper,
tenantWrapper,
)
containerResolver = containerResolverWrapper

Expand Down Expand Up @@ -674,6 +676,7 @@ func scanCreateSubCommand(
accessManagementWrapper wrappers.AccessManagementWrapper,
applicationsWrapper wrappers.ApplicationsWrapper,
featureFlagsWrapper wrappers.FeatureFlagsWrapper,
tenantWrapper wrappers.TenantConfigurationWrapper,
) *cobra.Command {
createScanCmd := &cobra.Command{
Use: "create",
Expand Down Expand Up @@ -707,6 +710,7 @@ func scanCreateSubCommand(
accessManagementWrapper,
applicationsWrapper,
featureFlagsWrapper,
tenantWrapper,
),
}
createScanCmd.PersistentFlags().Bool(commonParams.AsyncFlag, false, "Do not wait for scan completion")
Expand Down Expand Up @@ -945,6 +949,7 @@ func setupScanTypeProjectAndConfig(
accessManagementWrapper wrappers.AccessManagementWrapper,
featureFlagsWrapper wrappers.FeatureFlagsWrapper,
jwtWrapper wrappers.JWTWrapper,
tenantWrapper wrappers.TenantConfigurationWrapper,
) error {
userAllowedEngines, _ := jwtWrapper.GetAllowedEngines(featureFlagsWrapper)
var info map[string]interface{}
Expand Down Expand Up @@ -973,6 +978,7 @@ func setupScanTypeProjectAndConfig(
accessManagementWrapper,
applicationsWrapper,
featureFlagsWrapper,
tenantWrapper,
)
if findProjectErr != nil {
return findProjectErr
Expand Down Expand Up @@ -2391,6 +2397,7 @@ func runCreateScanCommand(
accessManagementWrapper wrappers.AccessManagementWrapper,
applicationsWrapper wrappers.ApplicationsWrapper,
featureFlagsWrapper wrappers.FeatureFlagsWrapper,
tenantWrapper wrappers.TenantConfigurationWrapper,
) func(cmd *cobra.Command, args []string) error {
return func(cmd *cobra.Command, args []string) error {
err := validateScanTypes(cmd, jwtWrapper, featureFlagsWrapper)
Expand Down Expand Up @@ -2440,6 +2447,7 @@ func runCreateScanCommand(
applicationsWrapper,
featureFlagsWrapper,
jwtWrapper,
tenantWrapper,
)
defer cleanUpTempZip(zipFilePath)
if err != nil {
Expand Down Expand Up @@ -2538,11 +2546,12 @@ func createScanModel(
applicationsWrapper wrappers.ApplicationsWrapper,
featureFlagsWrapper wrappers.FeatureFlagsWrapper,
jwtWrapper wrappers.JWTWrapper,
tenantWrapper wrappers.TenantConfigurationWrapper,
) (*wrappers.Scan, string, error) {
var input = []byte("{}")

// Define type, project and config in scan model
err := setupScanTypeProjectAndConfig(&input, cmd, projectsWrapper, groupsWrapper, scansWrapper, applicationsWrapper, accessManagementWrapper, featureFlagsWrapper, jwtWrapper)
err := setupScanTypeProjectAndConfig(&input, cmd, projectsWrapper, groupsWrapper, scansWrapper, applicationsWrapper, accessManagementWrapper, featureFlagsWrapper, jwtWrapper, tenantWrapper)
if err != nil {
return nil, "", err
}
Expand Down
20 changes: 19 additions & 1 deletion internal/commands/scan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4472,7 +4472,7 @@ func Test_CreateScanWithExistingProjectAssign_to_Application_FF_DirectAssociatio
file := createOutputFile(t, outputFileName)
defer deleteOutputFile(file)
defer logger.SetOutput(os.Stdout)

mock.Flag = wrappers.FeatureFlagResponseModel{Name: wrappers.DaMigrationEnabled, Status: false}
mock.Flag = wrappers.FeatureFlagResponseModel{Name: wrappers.DirectAssociationEnabled, Status: true}
baseArgs := []string{"scan", "create", "--project-name", "MOCK", "-s", ".", "--branch", "main", "--debug", "--application-name", mock.ExistingApplication}
execCmdNilAssertion(
Expand All @@ -4486,6 +4486,24 @@ func Test_CreateScanWithExistingProjectAssign_to_Application_FF_DirectAssociatio
assert.Equal(t, strings.Contains(stdoutString, "Successfully updated the application"), true, "Expected output: %s", "Successfully updated the application")
}

func Test_Create_Scan_With_DA_MIGRATION_And_ConfigurationEnabled_ShouldPass(t *testing.T) {
file := createOutputFile(t, outputFileName)
defer deleteOutputFile(file)
defer logger.SetOutput(os.Stdout)

mock.Flag = wrappers.FeatureFlagResponseModel{Name: wrappers.DaMigrationEnabled, Status: true}
baseArgs := []string{"scan", "create", "--project-name", "MOCK", "-s", ".", "--branch", "main", "--debug", "--application-name", mock.ExistingApplication}
execCmdNilAssertion(
t,
baseArgs...,
)
stdoutString, err := util.ReadFileAsString(file.Name())
if err != nil {
t.Fatalf("Failed to read log file: %v", err)
}
assert.Equal(t, strings.Contains(stdoutString, "Successfully updated the application"), true, "Expected output: %s", "Successfully updated the application")
}

// TestIsTarFileReference tests the tar file detection logic.
// Container-security scan-type related test function.
func TestIsTarFileReference(t *testing.T) {
Expand Down
8 changes: 4 additions & 4 deletions internal/commands/util/import.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func NewImportCommand(
accessManagementWrapper wrappers.AccessManagementWrapper,
byorWrapper wrappers.ByorWrapper,
applicationsWrapper wrappers.ApplicationsWrapper,
featureFlagsWrapper wrappers.FeatureFlagsWrapper) *cobra.Command {
featureFlagsWrapper wrappers.FeatureFlagsWrapper, tenantWrapper wrappers.TenantConfigurationWrapper) *cobra.Command {
cmd := &cobra.Command{
Use: "import",
Short: "Import SAST scan results",
Expand All @@ -37,7 +37,7 @@ func NewImportCommand(
`,
),
},
RunE: runImportCommand(projectsWrapper, uploadsWrapper, groupsWrapper, accessManagementWrapper, applicationsWrapper, byorWrapper, featureFlagsWrapper),
RunE: runImportCommand(projectsWrapper, uploadsWrapper, groupsWrapper, accessManagementWrapper, applicationsWrapper, byorWrapper, featureFlagsWrapper, tenantWrapper),
}

cmd.PersistentFlags().String(commonParams.ImportFilePath, "", "Path to the import file (sarif file or zip archive containing sarif files)")
Expand All @@ -53,7 +53,7 @@ func runImportCommand(
accessManagementWrapper wrappers.AccessManagementWrapper,
applicationsWrapper wrappers.ApplicationsWrapper,
byorWrapper wrappers.ByorWrapper,
featureFlagsWrapper wrappers.FeatureFlagsWrapper) func(cmd *cobra.Command, args []string) error {
featureFlagsWrapper wrappers.FeatureFlagsWrapper, tenantWrapper wrappers.TenantConfigurationWrapper) func(cmd *cobra.Command, args []string) error {
return func(cmd *cobra.Command, args []string) error {
importFilePath, err := validateFilePath(cmd)
if err != nil {
Expand All @@ -65,7 +65,7 @@ func runImportCommand(
return errors.Errorf(errorConstants.ProjectNameIsRequired)
}

projectID, err := services.FindProject(projectName, cmd, projectsWrapper, groupsWrapper, accessManagementWrapper, applicationsWrapper, featureFlagsWrapper)
projectID, err := services.FindProject(projectName, cmd, projectsWrapper, groupsWrapper, accessManagementWrapper, applicationsWrapper, featureFlagsWrapper, tenantWrapper)
if err != nil {
return err
}
Expand Down
8 changes: 8 additions & 0 deletions internal/commands/util/import_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ func TestImport_ImportSarifFileWithCorrectFlags_CreateImportSuccessfully(t *test
&mock.ByorMockWrapper{},
mock.ApplicationsMockWrapper{},
&mock.FeatureFlagsMockWrapper{},
&mock.TenantConfigurationMockWrapper{},
)
cmd.SetArgs([]string{"utils", "import", "--project-name", "my-project", "--import-file-path", "my-path.sarif"})
err := cmd.Execute()
Expand All @@ -36,6 +37,7 @@ func TestImport_ImportSarifFileProjectDoesntExist_CreateImportWithProvidedNewNam
&mock.ByorMockWrapper{},
mock.ApplicationsMockWrapper{},
&mock.FeatureFlagsMockWrapper{},
&mock.TenantConfigurationMockWrapper{},
)
cmd.SetArgs([]string{"utils", "import", "--project-name", "MOCK-PROJECT-NOT-EXIST", "--import-file-path", "my-path.sarif"})
err := cmd.Execute()
Expand All @@ -52,6 +54,7 @@ func TestImport_ImportSarifFileMissingImportFilePath_CreateImportReturnsErrorWit
&mock.ByorMockWrapper{},
mock.ApplicationsMockWrapper{},
&mock.FeatureFlagsMockWrapper{},
&mock.TenantConfigurationMockWrapper{},
)
cmd.SetArgs([]string{"utils", "import", "--project-name", "my-project"})
err := cmd.Execute()
Expand All @@ -67,6 +70,7 @@ func TestImport_ImportSarifFileEmptyImportFilePathValue_CreateImportReturnsError
&mock.ByorMockWrapper{},
mock.ApplicationsMockWrapper{},
&mock.FeatureFlagsMockWrapper{},
&mock.TenantConfigurationMockWrapper{},
)
cmd.SetArgs([]string{"utils", "import", "--project-name", "my-project", "--import-file-path", ""})
err := cmd.Execute()
Expand All @@ -82,6 +86,7 @@ func TestImport_ImportSarifFileMissingImportProjectName_CreateImportReturnsError
&mock.ByorMockWrapper{},
mock.ApplicationsMockWrapper{},
&mock.FeatureFlagsMockWrapper{},
&mock.TenantConfigurationMockWrapper{},
)
cmd.SetArgs([]string{"utils", "import", "--import-file-path", "my-path.zip"})
err := cmd.Execute()
Expand All @@ -97,6 +102,7 @@ func TestImport_ImportSarifFileProjectNameNotProvided_CreateImportWithProvidedNe
&mock.ByorMockWrapper{},
mock.ApplicationsMockWrapper{},
&mock.FeatureFlagsMockWrapper{},
&mock.TenantConfigurationMockWrapper{},
)
cmd.SetArgs([]string{"utils", "import", "--project-name", "", "--import-file-path", "my-path.sarif"})
err := cmd.Execute()
Expand All @@ -112,6 +118,7 @@ func TestImport_ImportSarifFileUnacceptedFileExtension_CreateImportReturnsErrorW
&mock.ByorMockWrapper{},
mock.ApplicationsMockWrapper{},
&mock.FeatureFlagsMockWrapper{},
&mock.TenantConfigurationMockWrapper{},
)
cmd.SetArgs([]string{"utils", "import", "--project-name", "MOCK-PROJECT-NOT-EXIST", "--import-file-path", "my-path.txt"})
err := cmd.Execute()
Expand All @@ -127,6 +134,7 @@ func TestImport_ImportSarifFileMissingExtension_CreateImportReturnsErrorWithCorr
&mock.ByorMockWrapper{},
mock.ApplicationsMockWrapper{},
&mock.FeatureFlagsMockWrapper{},
&mock.TenantConfigurationMockWrapper{},
)
cmd.SetArgs([]string{"utils", "import", "--project-name", "MOCK-PROJECT-NOT-EXIST", "--import-file-path", "some/path/no/extension/my-path"})
err := cmd.Execute()
Expand Down
2 changes: 1 addition & 1 deletion internal/commands/util/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func NewUtilsCommand(
},
}

importCmd := NewImportCommand(projectsWrapper, uploadsWrapper, groupsWrapper, accessManagementWrapper, byorWrapper, applicationsWrapper, featureFlagsWrapper)
importCmd := NewImportCommand(projectsWrapper, uploadsWrapper, groupsWrapper, accessManagementWrapper, byorWrapper, applicationsWrapper, featureFlagsWrapper, tenantWrapper)

envCheckCmd := NewEnvCheckCommand()

Expand Down
35 changes: 31 additions & 4 deletions internal/services/applications.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import (
)

const (
ApplicationRuleType = "project.name.in"
ApplicationRuleType = "project.name.in"
DirectAssociationKey = "scan.config.applications.directAssociations"
trueString = "true"
)

func createApplicationIds(applicationID, existingApplicationIds []string) []string {
Expand Down Expand Up @@ -64,7 +66,7 @@ func verifyApplicationNameExactMatch(applicationName string, resp *wrappers.Appl
return application
}

func findApplicationAndUpdate(applicationName string, applicationsWrapper wrappers.ApplicationsWrapper, projectName, projectID string, featureFlagsWrapper wrappers.FeatureFlagsWrapper) error {
func findApplicationAndUpdate(applicationName string, applicationsWrapper wrappers.ApplicationsWrapper, projectName, projectID string, featureFlagsWrapper wrappers.FeatureFlagsWrapper, tenantWrapper wrappers.TenantConfigurationWrapper) error {
if applicationName == "" {
logger.PrintfIfVerbose("No application name provided. Skipping application update")
return nil
Expand All @@ -77,8 +79,11 @@ func findApplicationAndUpdate(applicationName string, applicationsWrapper wrappe
return errors.Errorf("%s: %s", errorConstants.ApplicationNotFound, applicationName)
}

directAssociationEnabled, _ := wrappers.GetSpecificFeatureFlag(featureFlagsWrapper, wrappers.DirectAssociationEnabled)
if directAssociationEnabled.Status {
isEnabled, err := checkDirectAssociationEnabled(featureFlagsWrapper, tenantWrapper)
if err != nil {
return errors.Wrap(err, "error while checking if direct association is enabled")
}
if isEnabled {
err = associateProjectToApplication(applicationResp.ID, projectID, applicationResp.ProjectIds, applicationsWrapper)
if err != nil {
return err
Expand Down Expand Up @@ -107,6 +112,28 @@ func findApplicationAndUpdate(applicationName string, applicationsWrapper wrappe
return nil
}

func checkDirectAssociationEnabled(featureFlagsWrapper wrappers.FeatureFlagsWrapper, tenantWrapper wrappers.TenantConfigurationWrapper) (bool, error) {
directAssociationEnabled, _ := wrappers.GetSpecificFeatureFlag(featureFlagsWrapper, wrappers.DirectAssociationEnabled)
daMigrationEnabled, _ := wrappers.GetSpecificFeatureFlag(featureFlagsWrapper, wrappers.DaMigrationEnabled)
var isDAConfigurationEnabled bool
if daMigrationEnabled.Status {
tenantConfigurationResponse, errorModel, err := tenantWrapper.GetTenantConfiguration()
if err != nil {
return false, err
}
if errorModel != nil {
return false, errors.New(errorModel.Message)
}
if tenantConfigurationResponse != nil {
for _, resp := range *tenantConfigurationResponse {
if resp.Key == DirectAssociationKey && resp.Value == trueString {
isDAConfigurationEnabled = true
}
}
}
}
return (daMigrationEnabled.Status && isDAConfigurationEnabled) || directAssociationEnabled.Status, nil
}
func updateApplication(applicationModel *wrappers.ApplicationConfiguration, applicationWrapper wrappers.ApplicationsWrapper, applicationID string) error {
errorModel, err := applicationWrapper.Update(applicationID, applicationModel)
return handleApplicationUpdateResponse(errorModel, err)
Expand Down
3 changes: 2 additions & 1 deletion internal/services/projects.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ func FindProject(
accessManagementWrapper wrappers.AccessManagementWrapper,
applicationWrapper wrappers.ApplicationsWrapper,
featureFlagsWrapper wrappers.FeatureFlagsWrapper,
tenantWrapper wrappers.TenantConfigurationWrapper,
) (string, error) {
var isBranchPrimary bool
resp, err := GetProjectsCollectionByProjectName(projectName, projectsWrapper)
Expand All @@ -44,7 +45,7 @@ func FindProject(
for i := 0; i < len(resp.Projects); i++ {
project := resp.Projects[i]
if project.Name == projectName {
err = findApplicationAndUpdate(applicationName, applicationWrapper, projectName, project.ID, featureFlagsWrapper)
err = findApplicationAndUpdate(applicationName, applicationWrapper, projectName, project.ID, featureFlagsWrapper, tenantWrapper)
if err != nil {
return "", err
}
Expand Down
6 changes: 5 additions & 1 deletion internal/services/projects_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ func TestFindProject(t *testing.T) {
accessManagementWrapper wrappers.AccessManagementWrapper
applicationsWrapper wrappers.ApplicationsWrapper
featureFlagsWrapper wrappers.FeatureFlagsWrapper
tenantWrapper wrappers.TenantConfigurationWrapper
}
tests := []struct {
name string
Expand All @@ -37,6 +38,7 @@ func TestFindProject(t *testing.T) {
accessManagementWrapper: &mock.AccessManagementMockWrapper{},
applicationsWrapper: &mock.ApplicationsMockWrapper{},
featureFlagsWrapper: &mock.FeatureFlagsMockWrapper{},
tenantWrapper: &mock.TenantConfigurationMockWrapper{},
},
want: "MOCK",
wantErr: false,
Expand All @@ -51,6 +53,7 @@ func TestFindProject(t *testing.T) {
accessManagementWrapper: &mock.AccessManagementMockWrapper{},
applicationsWrapper: &mock.ApplicationsMockWrapper{},
featureFlagsWrapper: &mock.FeatureFlagsMockWrapper{},
tenantWrapper: &mock.TenantConfigurationMockWrapper{},
},
want: "ID-new-MOCK",
wantErr: false,
Expand All @@ -66,7 +69,8 @@ func TestFindProject(t *testing.T) {
ttt.args.groupsWrapper,
ttt.args.accessManagementWrapper,
ttt.args.applicationsWrapper,
ttt.args.featureFlagsWrapper)
ttt.args.featureFlagsWrapper,
ttt.args.tenantWrapper)
if (err != nil) != ttt.wantErr {
t.Errorf("FindProject() error = %v, wantErr %v", err, ttt.wantErr)
return
Expand Down
1 change: 1 addition & 0 deletions internal/wrappers/feature-flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const OssRealtimeEnabled = "OSS_REALTIME_ENABLED"
const ScsLicensingV2Enabled = "SSCS_NEW_LICENSING_ENABLED"
const SscsCommitHistoryEnabled = "SSCS_COMMIT_HISTORY_ENABLED"
const DirectAssociationEnabled = "DIRECT_APP_ASSOCIATION_ENABLED"
const DaMigrationEnabled = "DA_MIGRATION_ENABLED"
const maxRetries = 3
const IncreaseFileUploadLimit = "INCREASE_FILE_UPLOAD_LIMIT"
const ScaDeltaScanEnabled = "SCA_DELTASCAN_ENABLED"
Expand Down
4 changes: 4 additions & 0 deletions internal/wrappers/mock/tenant-mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ func (t TenantConfigurationMockWrapper) GetTenantConfiguration() (
Key: "scan.config.plugins.aiGuidedRemediationAiEngine",
Value: "azureai",
},
{
Key: "scan.config.applications.directAssociations",
Value: "true",
},
}
}
return &TenantConfiguration, nil, nil
Expand Down
Loading