github.com/ouraigua/jenkins-library@v0.0.0-20231028010029-fbeaf2f3aa9b/pkg/checkmarx/checkmarx.go (about)

     1  package checkmarx
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"net/http"
     9  	"net/url"
    10  	"strconv"
    11  	"strings"
    12  	"time"
    13  
    14  	"encoding/xml"
    15  
    16  	piperHttp "github.com/SAP/jenkins-library/pkg/http"
    17  	"github.com/SAP/jenkins-library/pkg/log"
    18  	"github.com/SAP/jenkins-library/pkg/piperutils"
    19  	"github.com/pkg/errors"
    20  	"github.com/sirupsen/logrus"
    21  )
    22  
    23  // ReportsDirectory defines the subfolder for the Checkmarx reports which are generated
    24  const ReportsDirectory = "checkmarx"
    25  
    26  // AuthToken - Structure to store OAuth2 token
    27  type AuthToken struct {
    28  	TokenType   string `json:"token_type"`
    29  	AccessToken string `json:"access_token"`
    30  	ExpiresIn   int    `json:"expires_in"`
    31  }
    32  
    33  // Preset - Project's Preset
    34  type Preset struct {
    35  	ID        int    `json:"id"`
    36  	Name      string `json:"name"`
    37  	OwnerName string `json:"ownerName"`
    38  	Link      Link   `json:"link"`
    39  }
    40  
    41  // Scan - Scan Structure
    42  type Scan struct {
    43  	ID   int  `json:"id"`
    44  	Link Link `json:"link"`
    45  }
    46  
    47  // ProjectCreateResult - ProjectCreateResult Structure
    48  type ProjectCreateResult struct {
    49  	ID   int  `json:"id"`
    50  	Link Link `json:"link"`
    51  }
    52  
    53  // ProjectBranchingResponse - ProjectBranchingResponse Structure
    54  type ProjectBranchingResponse struct {
    55  	ID   int  `json:"id"`
    56  	Link Link `json:"link"`
    57  }
    58  
    59  type BranchingStatus struct {
    60  	ID    int    `json:"id"`
    61  	Value string `json:"value"`
    62  }
    63  
    64  // ProjectBranchingStatusResponse - ProjectBranchingStatusResponse Structure
    65  type ProjectBranchingStatusResponse struct {
    66  	ID                int             `json:"id"`
    67  	OriginalProjectId int             `json:"originalProjectId"`
    68  	BranchedProjectId int             `json:"branchedProjectId"`
    69  	Status            BranchingStatus `json:"status"`
    70  	ErrorMessage      string          `json:"errorMessage"`
    71  }
    72  
    73  // Report - Report Structure
    74  type Report struct {
    75  	ReportID int   `json:"reportId"`
    76  	Links    Links `json:"links"`
    77  }
    78  
    79  // ResultsStatistics - ResultsStatistics Structure
    80  type ResultsStatistics struct {
    81  	High   int `json:"highSeverity"`
    82  	Medium int `json:"mediumSeverity"`
    83  	Low    int `json:"lowSeverity"`
    84  	Info   int `json:"infoSeverity"`
    85  }
    86  
    87  // ScanStatus - ScanStatus Structure
    88  type ScanStatus struct {
    89  	ID            int    `json:"id"`
    90  	Link          Link   `json:"link"`
    91  	Status        Status `json:"status"`
    92  	ScanType      string `json:"scanType"`
    93  	Comment       string `json:"comment"`
    94  	IsIncremental bool   `json:"isIncremental"`
    95  }
    96  
    97  // Status - Status Structure
    98  type Status struct {
    99  	ID      int              `json:"id"`
   100  	Name    string           `json:"name"`
   101  	Details ScanStatusDetail `json:"details"`
   102  }
   103  
   104  // ScanStatusDetail - ScanStatusDetail Structure
   105  type ScanStatusDetail struct {
   106  	Stage string `json:"stage"`
   107  	Step  string `json:"step"`
   108  }
   109  
   110  // ReportStatusResponse - ReportStatusResponse Structure
   111  type ReportStatusResponse struct {
   112  	Location    string       `json:"location"`
   113  	ContentType string       `json:"contentType"`
   114  	Status      ReportStatus `json:"status"`
   115  }
   116  
   117  // ReportStatus - ReportStatus Structure
   118  type ReportStatus struct {
   119  	ID    int    `json:"id"`
   120  	Value string `json:"value"`
   121  }
   122  
   123  // Project - Project Structure
   124  type Project struct {
   125  	ID                 int                `json:"id"`
   126  	TeamID             string             `json:"teamId"`
   127  	Name               string             `json:"name"`
   128  	IsPublic           bool               `json:"isPublic"`
   129  	SourceSettingsLink SourceSettingsLink `json:"sourceSettingsLink"`
   130  	Link               Link               `json:"link"`
   131  }
   132  
   133  // ScanSettings - scan settings at project level
   134  type ScanSettings struct {
   135  	Project             ProjectLink             `json:"project"`
   136  	Preset              PresetLink              `json:"preset"`
   137  	EngineConfiguration EngineConfigurationLink `json:"engineConfiguration" `
   138  }
   139  
   140  // ProjectLink - project link found in ScanSettings response
   141  type ProjectLink struct {
   142  	ProjectID int  `json:"id"`
   143  	Link      Link `json:"link"`
   144  }
   145  
   146  // PresetLink - preset link found in ScanSettings response
   147  type PresetLink struct {
   148  	PresetID int  `json:"id"`
   149  	Link     Link `json:"link"`
   150  }
   151  
   152  // EngineConfigurationLink - engine configuration link found in ScanSettings response
   153  type EngineConfigurationLink struct {
   154  	EngineConfigurationID int  `json:"id"`
   155  	Link                  Link `json:"link"`
   156  }
   157  
   158  // Team - Team Structure
   159  type Team struct {
   160  	ID       json.RawMessage `json:"id"`
   161  	FullName string          `json:"fullName"`
   162  }
   163  
   164  // Links - Links Structure
   165  type Links struct {
   166  	Report Link `json:"report"`
   167  	Status Link `json:"status"`
   168  }
   169  
   170  // Link - Link Structure
   171  type Link struct {
   172  	Rel string `json:"rel"`
   173  	URI string `json:"uri"`
   174  }
   175  
   176  // SourceSettingsLink - SourceSettingsLink Structure
   177  type SourceSettingsLink struct {
   178  	Type string `json:"type"`
   179  	Rel  string `json:"rel"`
   180  	URI  string `json:"uri"`
   181  }
   182  
   183  type ShortDescription struct {
   184  	Text string `json:"shortDescription"`
   185  }
   186  
   187  // DetailedResult - DetailedResult Structure
   188  type DetailedResult struct {
   189  	XMLName                  xml.Name `xml:"CxXMLResults"`
   190  	InitiatorName            string   `xml:"InitiatorName,attr"`
   191  	ScanID                   string   `xml:"ScanId,attr"`
   192  	Owner                    string   `xml:"Owner,attr"`
   193  	ProjectID                string   `xml:"ProjectId,attr"`
   194  	ProjectName              string   `xml:"ProjectName,attr"`
   195  	TeamFullPathOnReportDate string   `xml:"TeamFullPathOnReportDate,attr"`
   196  	DeepLink                 string   `xml:"DeepLink,attr"`
   197  	ScanStart                string   `xml:"ScanStart,attr"`
   198  	Preset                   string   `xml:"Preset,attr"`
   199  	ScanTime                 string   `xml:"ScanTime,attr"`
   200  	LinesOfCodeScanned       int      `xml:"LinesOfCodeScanned,attr"`
   201  	FilesScanned             int      `xml:"FilesScanned,attr"`
   202  	ReportCreationTime       string   `xml:"ReportCreationTime,attr"`
   203  	Team                     string   `xml:"Team,attr"`
   204  	CheckmarxVersion         string   `xml:"CheckmarxVersion,attr"`
   205  	ScanType                 string   `xml:"ScanType,attr"`
   206  	SourceOrigin             string   `xml:"SourceOrigin,attr"`
   207  	Visibility               string   `xml:"Visibility,attr"`
   208  	Queries                  []Query  `xml:"Query"`
   209  }
   210  
   211  // Query - Query Structure
   212  type Query struct {
   213  	XMLName xml.Name `xml:"Query"`
   214  	Name    string   `xml:"name,attr"`
   215  	Results []Result `xml:"Result"`
   216  }
   217  
   218  // Result - Result Structure
   219  type Result struct {
   220  	XMLName       xml.Name `xml:"Result"`
   221  	State         string   `xml:"state,attr"`
   222  	Severity      string   `xml:"Severity,attr"`
   223  	FalsePositive string   `xml:"FalsePositive,attr"`
   224  }
   225  
   226  // SystemInstance is the client communicating with the Checkmarx backend
   227  type SystemInstance struct {
   228  	serverURL string
   229  	username  string
   230  	password  string
   231  	client    piperHttp.Uploader
   232  	logger    *logrus.Entry
   233  }
   234  
   235  // System is the interface abstraction of a specific SystemIns
   236  type System interface {
   237  	FilterPresetByName(presets []Preset, presetName string) Preset
   238  	FilterPresetByID(presets []Preset, presetID int) Preset
   239  	FilterProjectByName(projects []Project, projectName string) Project
   240  	FilterTeamByName(teams []Team, teamName string) (Team, error)
   241  	FilterTeamByID(teams []Team, teamID json.RawMessage) Team
   242  	DownloadReport(reportID int) ([]byte, error)
   243  	GetReportStatus(reportID int) (ReportStatusResponse, error)
   244  	RequestNewReport(scanID int, reportType string) (Report, error)
   245  	GetResults(scanID int) ResultsStatistics
   246  	GetScanStatusAndDetail(scanID int) (string, ScanStatusDetail)
   247  	GetScans(projectID int) ([]ScanStatus, error)
   248  	ScanProject(projectID int, isIncremental, isPublic, forceScan bool) (Scan, error)
   249  	UpdateProjectConfiguration(projectID int, presetID int, engineConfigurationID string) error
   250  	UpdateProjectExcludeSettings(projectID int, excludeFolders string, excludeFiles string) error
   251  	UploadProjectSourceCode(projectID int, zipFile string) error
   252  	CreateProject(projectName, teamID string) (ProjectCreateResult, error)
   253  	CreateBranch(projectID int, branchName string) int
   254  	GetPresets() []Preset
   255  	GetProjectByID(projectID int) (Project, error)
   256  	GetProjectsByNameAndTeam(projectName, teamID string) ([]Project, error)
   257  	GetProjects() ([]Project, error)
   258  	GetShortDescription(scanID int, pathID int) (ShortDescription, error)
   259  	GetTeams() []Team
   260  }
   261  
   262  // NewSystemInstance returns a new Checkmarx client for communicating with the backend
   263  func NewSystemInstance(client piperHttp.Uploader, serverURL, username, password string) (*SystemInstance, error) {
   264  	loggerInstance := log.Entry().WithField("package", "SAP/jenkins-library/pkg/checkmarx")
   265  	sys := &SystemInstance{
   266  		serverURL: serverURL,
   267  		username:  username,
   268  		password:  password,
   269  		client:    client,
   270  		logger:    loggerInstance,
   271  	}
   272  
   273  	token, err := sys.getOAuth2Token()
   274  	if err != nil {
   275  		return sys, errors.Wrap(err, "Error fetching oAuth token")
   276  	}
   277  
   278  	log.RegisterSecret(token)
   279  
   280  	options := piperHttp.ClientOptions{
   281  		Token:            token,
   282  		TransportTimeout: time.Minute * 15,
   283  	}
   284  	sys.client.SetOptions(options)
   285  
   286  	return sys, nil
   287  }
   288  
   289  func sendRequest(sys *SystemInstance, method, url string, body io.Reader, header http.Header) ([]byte, error) {
   290  	return sendRequestInternal(sys, method, url, body, header, []int{})
   291  }
   292  
   293  func sendRequestInternal(sys *SystemInstance, method, url string, body io.Reader, header http.Header, acceptedErrorCodes []int) ([]byte, error) {
   294  	var requestBody io.Reader
   295  	var requestBodyCopy io.Reader
   296  	if body != nil {
   297  		closer := io.NopCloser(body)
   298  		bodyBytes, _ := io.ReadAll(closer)
   299  		requestBody = bytes.NewBuffer(bodyBytes)
   300  		requestBodyCopy = bytes.NewBuffer(bodyBytes)
   301  		defer closer.Close()
   302  	}
   303  	response, err := sys.client.SendRequest(method, fmt.Sprintf("%v/cxrestapi%v", sys.serverURL, url), requestBody, header, nil)
   304  	if err != nil && (response == nil || !piperutils.ContainsInt(acceptedErrorCodes, response.StatusCode)) {
   305  		sys.recordRequestDetailsInErrorCase(requestBodyCopy, response)
   306  		sys.logger.Errorf("HTTP request failed with error: %s", err)
   307  		return nil, err
   308  	}
   309  
   310  	data, _ := io.ReadAll(response.Body)
   311  	sys.logger.Debugf("Valid response body: %v", string(data))
   312  	defer response.Body.Close()
   313  	return data, nil
   314  }
   315  
   316  func (sys *SystemInstance) recordRequestDetailsInErrorCase(requestBody io.Reader, response *http.Response) {
   317  	if requestBody != nil {
   318  		data, _ := io.ReadAll(io.NopCloser(requestBody))
   319  		sys.logger.Errorf("Request body: %s", data)
   320  	}
   321  	if response != nil && response.Body != nil {
   322  		data, _ := io.ReadAll(response.Body)
   323  		sys.logger.Errorf("Response body: %s", data)
   324  		response.Body.Close()
   325  	}
   326  }
   327  
   328  func (sys *SystemInstance) getOAuth2Token() (string, error) {
   329  	body := url.Values{
   330  		"username":      {sys.username},
   331  		"password":      {sys.password},
   332  		"grant_type":    {"password"},
   333  		"scope":         {"sast_rest_api"},
   334  		"client_id":     {"resource_owner_client"},
   335  		"client_secret": {"014DF517-39D1-4453-B7B3-9930C563627C"},
   336  	}
   337  	header := http.Header{}
   338  	header.Add("Content-type", "application/x-www-form-urlencoded")
   339  	data, err := sendRequest(sys, http.MethodPost, "/auth/identity/connect/token", strings.NewReader(body.Encode()), header)
   340  	if err != nil {
   341  		return "", err
   342  	}
   343  
   344  	var token AuthToken
   345  	json.Unmarshal(data, &token)
   346  	return token.TokenType + " " + token.AccessToken, nil
   347  }
   348  
   349  // GetTeams returns the teams the user is assigned to
   350  func (sys *SystemInstance) GetTeams() []Team {
   351  	sys.logger.Debug("Getting Teams...")
   352  	var teams []Team
   353  
   354  	data, err := sendRequest(sys, http.MethodGet, "/auth/teams", nil, nil)
   355  	if err != nil {
   356  		sys.logger.Errorf("Fetching teams failed: %s", err)
   357  		return teams
   358  	}
   359  
   360  	json.Unmarshal(data, &teams)
   361  	return teams
   362  }
   363  
   364  // GetProjects returns the projects defined in the Checkmarx backend which the user has access to
   365  func (sys *SystemInstance) GetProjects() ([]Project, error) {
   366  	return sys.GetProjectsByNameAndTeam("", "")
   367  }
   368  
   369  // GetProjectByID returns the project addressed by projectID from the Checkmarx backend which the user has access to
   370  func (sys *SystemInstance) GetProjectByID(projectID int) (Project, error) {
   371  	sys.logger.Debugf("Getting Project with ID %v...", projectID)
   372  	var project Project
   373  
   374  	data, err := sendRequest(sys, http.MethodGet, fmt.Sprintf("/projects/%v", projectID), nil, nil)
   375  	if err != nil {
   376  		return project, errors.Wrapf(err, "fetching project %v failed", projectID)
   377  	}
   378  
   379  	json.Unmarshal(data, &project)
   380  	return project, nil
   381  }
   382  
   383  // GetProjectsByNameAndTeam returns the project addressed by projectID from the Checkmarx backend which the user has access to
   384  func (sys *SystemInstance) GetProjectsByNameAndTeam(projectName, teamID string) ([]Project, error) {
   385  	sys.logger.Debugf("Getting projects with name %v of team %v...", projectName, teamID)
   386  	var projects []Project
   387  	header := http.Header{}
   388  	header.Set("Accept-Type", "application/json")
   389  	var data []byte
   390  	var err error
   391  	if len(teamID) > 0 && len(projectName) > 0 {
   392  		body := url.Values{
   393  			"projectName": {projectName},
   394  			"teamId":      {teamID},
   395  		}
   396  		data, err = sendRequestInternal(sys, http.MethodGet, fmt.Sprintf("/projects?%v", body.Encode()), nil, header, []int{404})
   397  	} else {
   398  		data, err = sendRequestInternal(sys, http.MethodGet, "/projects", nil, header, []int{404})
   399  	}
   400  	if err != nil {
   401  		return projects, errors.Wrapf(err, "fetching project %v failed", projectName)
   402  	}
   403  
   404  	json.Unmarshal(data, &projects)
   405  	return projects, nil
   406  }
   407  
   408  // CreateProject creates a new project in the Checkmarx backend
   409  func (sys *SystemInstance) CreateProject(projectName, teamID string) (ProjectCreateResult, error) {
   410  	var result ProjectCreateResult
   411  	jsonData := map[string]interface{}{
   412  		"name":       projectName,
   413  		"owningTeam": teamID,
   414  		"isPublic":   true,
   415  	}
   416  
   417  	jsonValue, err := json.Marshal(jsonData)
   418  	if err != nil {
   419  		return result, errors.Wrapf(err, "failed to marshal project data")
   420  	}
   421  
   422  	header := http.Header{}
   423  	header.Set("Content-Type", "application/json")
   424  
   425  	data, err := sendRequest(sys, http.MethodPost, "/projects", bytes.NewBuffer(jsonValue), header)
   426  	if err != nil {
   427  		return result, errors.Wrapf(err, "failed to create project %v", projectName)
   428  	}
   429  
   430  	json.Unmarshal(data, &result)
   431  	return result, nil
   432  }
   433  
   434  // CreateBranch creates a branch of an existing project in the Checkmarx backend
   435  func (sys *SystemInstance) CreateBranch(projectID int, branchName string) int {
   436  	jsonData := map[string]interface{}{
   437  		"name": branchName,
   438  	}
   439  
   440  	jsonValue, err := json.Marshal(jsonData)
   441  	if err != nil {
   442  		sys.logger.Errorf("Error Marshal: %s", err)
   443  		return 0
   444  	}
   445  
   446  	header := http.Header{}
   447  	header.Set("Content-Type", "application/json")
   448  	data, err := sendRequest(sys, http.MethodPost, fmt.Sprintf("/projects/%v/branch", projectID), bytes.NewBuffer(jsonValue), header)
   449  	if err != nil {
   450  		sys.logger.Errorf("Failed to create project: %s", err)
   451  		return 0
   452  	}
   453  
   454  	var branchingResponse ProjectBranchingResponse
   455  	json.Unmarshal(data, &branchingResponse)
   456  	branchedProjectId := branchingResponse.ID
   457  
   458  	branchingStatusId := 0 // 0 Started, 1 InProgress, 2 Completed, 3 Failed
   459  	i := 0
   460  	for branchingStatusId != 2 {
   461  		dataBranchingStatus, err := sendRequest(sys, http.MethodGet, fmt.Sprintf("/projects/branch/%v", branchedProjectId), nil, header)
   462  		if err != nil {
   463  			sys.logger.Warnf("Failed to poll status of branching process: %s", err)
   464  		} else {
   465  			var branchingStatusResponse ProjectBranchingStatusResponse
   466  			json.Unmarshal(dataBranchingStatus, &branchingStatusResponse)
   467  			branchingStatusId = branchingStatusResponse.Status.ID
   468  			branchingStatusValue := branchingStatusResponse.Status.Value
   469  			sys.logger.Debugf("Branching process status: %s", branchingStatusValue)
   470  			if branchingStatusId == 2 {
   471  				sys.logger.Debug("Branching process completed successfuly")
   472  				break
   473  			} else if branchingStatusId == 3 {
   474  				sys.logger.Errorf("Branching process failed. Error is: %s", branchingStatusResponse.ErrorMessage)
   475  				return 0
   476  			}
   477  		}
   478  		if i >= 30 {
   479  			// time out after 5 minutes
   480  			sys.logger.Errorf("Branching process timed out.")
   481  			return 0
   482  		}
   483  		i++
   484  		time.Sleep(10 * time.Second)
   485  	}
   486  	return branchedProjectId
   487  }
   488  
   489  // UploadProjectSourceCode zips and uploads the project sources for scanning
   490  func (sys *SystemInstance) UploadProjectSourceCode(projectID int, zipFile string) error {
   491  	sys.logger.Debug("Starting to upload files...")
   492  
   493  	header := http.Header{}
   494  	header.Add("Accept-Encoding", "gzip,deflate")
   495  	header.Add("Accept", "text/plain")
   496  	resp, err := sys.client.UploadFile(fmt.Sprintf("%v/cxrestapi/projects/%v/sourceCode/attachments", sys.serverURL, projectID), zipFile, "zippedSource", header, nil, "form")
   497  	if err != nil {
   498  		return errors.Wrap(err, "failed to uploaded zipped sources")
   499  	}
   500  
   501  	data, err := io.ReadAll(resp.Body)
   502  	defer resp.Body.Close()
   503  	if err != nil {
   504  		return errors.Wrap(err, "error reading the response data")
   505  	}
   506  
   507  	responseData := make(map[string]string)
   508  	json.Unmarshal(data, &responseData)
   509  
   510  	if resp.StatusCode == http.StatusNoContent {
   511  		return nil
   512  	}
   513  
   514  	sys.logger.Debugf("Body %s", data)
   515  	return errors.Wrapf(err, "error writing the request's body, status: %s", resp.Status)
   516  }
   517  
   518  // UpdateProjectExcludeSettings updates the exclude configuration of the project
   519  func (sys *SystemInstance) UpdateProjectExcludeSettings(projectID int, excludeFolders string, excludeFiles string) error {
   520  	jsonData := map[string]string{
   521  		"excludeFoldersPattern": excludeFolders,
   522  		"excludeFilesPattern":   excludeFiles,
   523  	}
   524  
   525  	jsonValue, err := json.Marshal(jsonData)
   526  	if err != nil {
   527  		return errors.Wrap(err, "error marhalling project exclude settings")
   528  	}
   529  
   530  	header := http.Header{}
   531  	header.Set("Content-Type", "application/json")
   532  	_, err = sendRequest(sys, http.MethodPut, fmt.Sprintf("/projects/%v/sourceCode/excludeSettings", projectID), bytes.NewBuffer(jsonValue), header)
   533  	if err != nil {
   534  		return errors.Wrap(err, "request to checkmarx system failed")
   535  	}
   536  
   537  	return nil
   538  }
   539  
   540  // GetPresets loads the preset values defined in the Checkmarx backend
   541  func (sys *SystemInstance) GetPresets() []Preset {
   542  	sys.logger.Debug("Getting Presets...")
   543  	var presets []Preset
   544  
   545  	data, err := sendRequest(sys, http.MethodGet, "/sast/presets", nil, nil)
   546  	if err != nil {
   547  		sys.logger.Errorf("Fetching presets failed: %s", err)
   548  		return presets
   549  	}
   550  
   551  	json.Unmarshal(data, &presets)
   552  	return presets
   553  }
   554  
   555  // UpdateProjectConfiguration updates the configuration of the project addressed by projectID
   556  func (sys *SystemInstance) UpdateProjectConfiguration(projectID int, presetID int, engineConfigurationID string) error {
   557  	engineConfigID, _ := strconv.Atoi(engineConfigurationID)
   558  
   559  	var projectScanSettings ScanSettings
   560  	header := http.Header{}
   561  	header.Set("Content-Type", "application/json")
   562  	data, err := sendRequest(sys, http.MethodGet, fmt.Sprintf("/sast/scanSettings/%v", projectID), nil, header)
   563  	if err != nil {
   564  		// if an error happens, try to update the config anyway
   565  		sys.logger.Warnf("Failed to fetch scan settings of project %v: %s", projectID, err)
   566  	} else {
   567  		// Check if the current project config needs to be updated
   568  		json.Unmarshal(data, &projectScanSettings)
   569  		if projectScanSettings.Preset.PresetID == presetID && (projectScanSettings.EngineConfiguration.EngineConfigurationID == engineConfigID || engineConfigID == 0) {
   570  			sys.logger.Debugf("Project configuration does not need to be updated")
   571  			return nil
   572  		}
   573  	}
   574  
   575  	// use the project-level value to configure the project if no value was provided in piper config
   576  	if engineConfigID == 0 {
   577  		engineConfigID = projectScanSettings.EngineConfiguration.EngineConfigurationID
   578  	}
   579  
   580  	jsonData := map[string]interface{}{
   581  		"projectId":             projectID,
   582  		"presetId":              presetID,
   583  		"engineConfigurationId": engineConfigID,
   584  	}
   585  
   586  	jsonValue, err := json.Marshal(jsonData)
   587  	if err != nil {
   588  		return errors.Wrapf(err, "error marshalling project data")
   589  	}
   590  
   591  	_, err = sendRequest(sys, http.MethodPost, "/sast/scanSettings", bytes.NewBuffer(jsonValue), header)
   592  	if err != nil {
   593  		return errors.Wrapf(err, "request to checkmarx system failed")
   594  	}
   595  	sys.logger.Debugf("Project configuration updated")
   596  
   597  	return nil
   598  }
   599  
   600  // ScanProject triggers a scan on the project addressed by projectID
   601  func (sys *SystemInstance) ScanProject(projectID int, isIncremental, isPublic, forceScan bool) (Scan, error) {
   602  	scan := Scan{}
   603  	jsonData := map[string]interface{}{
   604  		"projectId":     projectID,
   605  		"isIncremental": isIncremental,
   606  		"isPublic":      isPublic,
   607  		"forceScan":     forceScan,
   608  		"comment":       "Scan From Golang Script",
   609  	}
   610  
   611  	jsonValue, _ := json.Marshal(jsonData)
   612  
   613  	header := http.Header{}
   614  	header.Set("cxOrigin", "GolangScript")
   615  	header.Set("Content-Type", "application/json")
   616  	data, err := sendRequest(sys, http.MethodPost, "/sast/scans", bytes.NewBuffer(jsonValue), header)
   617  	if err != nil {
   618  		sys.logger.Errorf("Failed to trigger scan of project %v: %s", projectID, err)
   619  		return scan, errors.Wrapf(err, "Failed to trigger scan of project %v", projectID)
   620  	}
   621  
   622  	json.Unmarshal(data, &scan)
   623  	return scan, nil
   624  }
   625  
   626  // GetScans returns all scan status on the project addressed by projectID
   627  func (sys *SystemInstance) GetScans(projectID int) ([]ScanStatus, error) {
   628  	scans := []ScanStatus{}
   629  	body := url.Values{
   630  		"projectId": {fmt.Sprintf("%v", projectID)},
   631  		"last":      {fmt.Sprintf("%v", 20)},
   632  	}
   633  
   634  	header := http.Header{}
   635  	header.Set("cxOrigin", "GolangScript")
   636  	header.Set("Accept-Type", "application/json")
   637  	data, err := sendRequest(sys, http.MethodGet, fmt.Sprintf("/sast/scans?%v", body.Encode()), nil, header)
   638  	if err != nil {
   639  		sys.logger.Errorf("Failed to fetch scans of project %v: %s", projectID, err)
   640  		return scans, errors.Wrapf(err, "failed to fetch scans of project %v", projectID)
   641  	}
   642  
   643  	json.Unmarshal(data, &scans)
   644  	return scans, nil
   645  }
   646  
   647  // GetScanStatusAndDetail returns the status of the scan addressed by scanID
   648  func (sys *SystemInstance) GetScanStatusAndDetail(scanID int) (string, ScanStatusDetail) {
   649  	var scanStatus ScanStatus
   650  
   651  	data, err := sendRequest(sys, http.MethodGet, fmt.Sprintf("/sast/scans/%v", scanID), nil, nil)
   652  	if err != nil {
   653  		sys.logger.Errorf("Failed to get scan status for scanID %v: %s", scanID, err)
   654  		return "Failed", ScanStatusDetail{}
   655  	}
   656  
   657  	json.Unmarshal(data, &scanStatus)
   658  	return scanStatus.Status.Name, scanStatus.Status.Details
   659  }
   660  
   661  // GetResults returns the results of the scan addressed by scanID
   662  func (sys *SystemInstance) GetResults(scanID int) ResultsStatistics {
   663  	var results ResultsStatistics
   664  
   665  	data, err := sendRequest(sys, http.MethodGet, fmt.Sprintf("/sast/scans/%v/resultsStatistics", scanID), nil, nil)
   666  	if err != nil {
   667  		sys.logger.Errorf("Failed to fetch scan results for scanID %v: %s", scanID, err)
   668  		return results
   669  	}
   670  
   671  	json.Unmarshal(data, &results)
   672  	return results
   673  }
   674  
   675  // RequestNewReport triggers the generation of a  report for a specific scan addressed by scanID
   676  func (sys *SystemInstance) RequestNewReport(scanID int, reportType string) (Report, error) {
   677  	report := Report{}
   678  	jsonData := map[string]interface{}{
   679  		"scanId":     scanID,
   680  		"reportType": reportType,
   681  		"comment":    "Scan report triggered by Piper",
   682  	}
   683  
   684  	jsonValue, _ := json.Marshal(jsonData)
   685  
   686  	header := http.Header{}
   687  	header.Set("cxOrigin", "GolangScript")
   688  	header.Set("Content-Type", "application/json")
   689  	data, err := sendRequest(sys, http.MethodPost, "/reports/sastScan", bytes.NewBuffer(jsonValue), header)
   690  	if err != nil {
   691  		return report, errors.Wrapf(err, "Failed to trigger report generation for scan %v", scanID)
   692  	}
   693  
   694  	json.Unmarshal(data, &report)
   695  	return report, nil
   696  }
   697  
   698  // GetReportStatus returns the status of the report generation process
   699  func (sys *SystemInstance) GetReportStatus(reportID int) (ReportStatusResponse, error) {
   700  	var response ReportStatusResponse
   701  
   702  	header := http.Header{}
   703  	header.Set("Accept", "application/json")
   704  	data, err := sendRequest(sys, http.MethodGet, fmt.Sprintf("/reports/sastScan/%v/status", reportID), nil, header)
   705  	if err != nil {
   706  		sys.logger.Errorf("Failed to fetch report status for reportID %v: %s", reportID, err)
   707  		return response, errors.Wrapf(err, "failed to fetch report status for reportID %v", reportID)
   708  	}
   709  
   710  	json.Unmarshal(data, &response)
   711  	return response, nil
   712  }
   713  
   714  // GetShortDescription returns the short description for an issue with a scanID and pathID
   715  func (sys *SystemInstance) GetShortDescription(scanID int, pathID int) (ShortDescription, error) {
   716  	var shortDescription ShortDescription
   717  
   718  	data, err := sendRequest(sys, http.MethodGet, fmt.Sprintf("/sast/scans/%v/results/%v/shortDescription", scanID, pathID), nil, nil)
   719  	if err != nil {
   720  		sys.logger.Errorf("Failed to get short description for scanID %v and pathID %v: %s", scanID, pathID, err)
   721  		return shortDescription, err
   722  	}
   723  
   724  	json.Unmarshal(data, &shortDescription)
   725  	return shortDescription, nil
   726  }
   727  
   728  // DownloadReport downloads the report addressed by reportID and returns the XML contents
   729  func (sys *SystemInstance) DownloadReport(reportID int) ([]byte, error) {
   730  	header := http.Header{}
   731  	header.Set("Accept", "application/json")
   732  	data, err := sendRequest(sys, http.MethodGet, fmt.Sprintf("/reports/sastScan/%v", reportID), nil, header)
   733  	if err != nil {
   734  		return []byte{}, errors.Wrapf(err, "failed to download report with reportID %v", reportID)
   735  	}
   736  	return data, nil
   737  }
   738  
   739  // FilterTeamByName filters a team by its name
   740  func (sys *SystemInstance) FilterTeamByName(teams []Team, teamName string) (Team, error) {
   741  	for _, team := range teams {
   742  		if team.FullName == teamName || team.FullName == strings.ReplaceAll(teamName, `\`, `/`) {
   743  			return team, nil
   744  		}
   745  	}
   746  	return Team{}, errors.New("Failed to find team with name " + teamName)
   747  }
   748  
   749  // FilterTeamByID filters a team by its ID
   750  func (sys *SystemInstance) FilterTeamByID(teams []Team, teamID json.RawMessage) Team {
   751  	teamIDBytes1, _ := teamID.MarshalJSON()
   752  	for _, team := range teams {
   753  		teamIDBytes2, _ := team.ID.MarshalJSON()
   754  		if bytes.Compare(teamIDBytes1, teamIDBytes2) == 0 {
   755  			return team
   756  		}
   757  	}
   758  	return Team{}
   759  }
   760  
   761  // FilterProjectByName filters a project by its name
   762  func (sys *SystemInstance) FilterProjectByName(projects []Project, projectName string) Project {
   763  	for _, project := range projects {
   764  		if project.Name == projectName {
   765  			sys.logger.Debugf("Filtered project with name %v", project.Name)
   766  			return project
   767  		}
   768  	}
   769  	return Project{}
   770  }
   771  
   772  // FilterPresetByName filters a preset by its name
   773  func (sys *SystemInstance) FilterPresetByName(presets []Preset, presetName string) Preset {
   774  	for _, preset := range presets {
   775  		if preset.Name == presetName {
   776  			return preset
   777  		}
   778  	}
   779  	return Preset{}
   780  }
   781  
   782  // FilterPresetByID filters a preset by its name
   783  func (sys *SystemInstance) FilterPresetByID(presets []Preset, presetID int) Preset {
   784  	for _, preset := range presets {
   785  		if preset.ID == presetID {
   786  			return preset
   787  		}
   788  	}
   789  	return Preset{}
   790  }