github.com/jaylevin/jenkins-library@v1.230.4/pkg/checkmarx/checkmarx.go (about)

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