package fortify import ( "bytes" "encoding/base64" "fmt" "io" "net/http" "net/url" "os" "strings" "time" ff "github.com/piper-validation/fortify-client-go/fortify" "github.com/piper-validation/fortify-client-go/fortify/artifact_of_project_version_controller" "github.com/piper-validation/fortify-client-go/fortify/attribute_of_project_version_controller" "github.com/piper-validation/fortify-client-go/fortify/auth_entity_of_project_version_controller" "github.com/piper-validation/fortify-client-go/fortify/file_token_controller" "github.com/piper-validation/fortify-client-go/fortify/filter_set_of_project_version_controller" "github.com/piper-validation/fortify-client-go/fortify/issue_audit_comment_of_issue_controller" "github.com/piper-validation/fortify-client-go/fortify/issue_group_of_project_version_controller" "github.com/piper-validation/fortify-client-go/fortify/issue_of_project_version_controller" "github.com/piper-validation/fortify-client-go/fortify/issue_selector_set_of_project_version_controller" "github.com/piper-validation/fortify-client-go/fortify/issue_statistics_of_project_version_controller" "github.com/piper-validation/fortify-client-go/fortify/project_controller" "github.com/piper-validation/fortify-client-go/fortify/project_version_controller" "github.com/piper-validation/fortify-client-go/fortify/project_version_of_project_controller" "github.com/piper-validation/fortify-client-go/fortify/saved_report_controller" "github.com/piper-validation/fortify-client-go/models" piperHttp "github.com/SAP/jenkins-library/pkg/http" "github.com/SAP/jenkins-library/pkg/log" "github.com/SAP/jenkins-library/pkg/piperutils" "github.com/go-openapi/runtime" "github.com/go-openapi/strfmt" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) // ReportsDirectory defines the subfolder for the Fortify reports which are generated const ReportsDirectory = "fortify" // System is the interface abstraction of a specific SystemInstance type System interface { GetProjectByName(name string, autoCreate bool, projectVersion string) (*models.Project, error) GetProjectVersionDetailsByProjectIDAndVersionName(id int64, name string, autoCreate bool, projectName string) (*models.ProjectVersion, error) GetProjectVersionAttributesByProjectVersionID(id int64) ([]*models.Attribute, error) SetProjectVersionAttributesByProjectVersionID(id int64, attributes []*models.Attribute) ([]*models.Attribute, error) CreateProjectVersionIfNotExist(projectName, projectVersionName, description string) (*models.ProjectVersion, error) LookupOrCreateProjectVersionDetailsForPullRequest(projectID int64, masterProjectVersion *models.ProjectVersion, pullRequestName string) (*models.ProjectVersion, error) CreateProjectVersion(version *models.ProjectVersion) (*models.ProjectVersion, error) ProjectVersionCopyFromPartial(sourceID, targetID int64) error ProjectVersionCopyCurrentState(sourceID, targetID int64) error ProjectVersionCopyPermissions(sourceID, targetID int64) error CommitProjectVersion(id int64) (*models.ProjectVersion, error) MergeProjectVersionStateOfPRIntoMaster(downloadEndpoint, uploadEndpoint string, masterProjectID, masterProjectVersionID int64, pullRequestName string) error GetArtifactsOfProjectVersion(id int64) ([]*models.Artifact, error) GetFilterSetOfProjectVersionByTitle(id int64, title string) (*models.FilterSet, error) GetIssueFilterSelectorOfProjectVersionByName(id int64, names []string, options []string) (*models.IssueFilterSelectorSet, error) GetFilterSetByDisplayName(issueFilterSelectorSet *models.IssueFilterSelectorSet, name string) *models.IssueFilterSelector GetProjectIssuesByIDAndFilterSetGroupedBySelector(id int64, filter, filterSetGUID string, issueFilterSelectorSet *models.IssueFilterSelectorSet) ([]*models.ProjectVersionIssueGroup, error) ReduceIssueFilterSelectorSet(issueFilterSelectorSet *models.IssueFilterSelectorSet, names []string, options []string) *models.IssueFilterSelectorSet GetIssueStatisticsOfProjectVersion(id int64) ([]*models.IssueStatistics, error) GenerateQGateReport(projectID, projectVersionID, reportTemplateID int64, projectName, projectVersionName, reportFormat string) (*models.SavedReport, error) GetReportDetails(id int64) (*models.SavedReport, error) GetIssueDetails(projectVersionId int64, issueInstanceId string) ([]*models.ProjectVersionIssue, error) GetAllIssueDetails(projectVersionId int64) ([]*models.ProjectVersionIssue, error) GetIssueComments(parentId int64) ([]*models.IssueAuditComment, error) UploadResultFile(endpoint, file string, projectVersionID int64) error DownloadReportFile(endpoint string, reportID int64) ([]byte, error) DownloadResultFile(endpoint string, projectVersionID int64) ([]byte, error) } // SystemInstance is the specific instance type SystemInstance struct { timeout time.Duration token string serverURL string client *ff.Fortify httpClient *piperHttp.Client logger *logrus.Entry } // NewSystemInstance - creates an returns a new SystemInstance func NewSystemInstance(serverURL, apiEndpoint, authToken string, timeout time.Duration) *SystemInstance { // If serverURL ends in a trailing slash, UploadResultFile() will construct a URL with two or more // consecutive slashes and actually fail with a 503. https://github.com/SAP/jenkins-library/issues/1826 // Also, since the step outputs a lot of URLs to the log, those will look nicer without redundant slashes. serverURL = strings.TrimRight(serverURL, "/") format := strfmt.Default dateTimeFormat := models.Iso8601MilliDateTime{} format.Add("datetime", &dateTimeFormat, models.IsDateTime) clientInstance := ff.NewHTTPClientWithConfig(format, createTransportConfig(serverURL, apiEndpoint)) encodedAuthToken := base64EndodePlainToken(authToken) httpClientInstance := &piperHttp.Client{} httpClientOptions := piperHttp.ClientOptions{Token: "FortifyToken " + encodedAuthToken, TransportTimeout: timeout} httpClientInstance.SetOptions(httpClientOptions) return NewSystemInstanceForClient(clientInstance, httpClientInstance, serverURL, encodedAuthToken, timeout) } func createTransportConfig(serverURL, apiEndpoint string) *ff.TransportConfig { scheme, host := splitSchemeAndHost(serverURL) host, hostEndpoint := splitHostAndEndpoint(host) // Cleaning up any slashes here is mostly for cleaner log-output. hostEndpoint = strings.TrimRight(hostEndpoint, "/") apiEndpoint = strings.Trim(apiEndpoint, "/") return &ff.TransportConfig{ Host: host, Schemes: []string{scheme}, BasePath: fmt.Sprintf("%v/%v", hostEndpoint, apiEndpoint)} } func splitSchemeAndHost(url string) (scheme, host string) { schemeEnd := strings.Index(url, "://") if schemeEnd >= 0 { scheme = url[0:schemeEnd] host = url[schemeEnd+3:] } else { scheme = "https" host = url } return } func splitHostAndEndpoint(urlWithoutScheme string) (host, endpoint string) { hostEnd := strings.Index(urlWithoutScheme, "/") if hostEnd >= 0 { host = urlWithoutScheme[0:hostEnd] endpoint = urlWithoutScheme[hostEnd+1:] } else { host = urlWithoutScheme endpoint = "" } return } func base64EndodePlainToken(authToken string) (encodedAuthToken string) { isEncoded := strings.Index(authToken, "-") < 0 if isEncoded { return authToken } return base64.StdEncoding.EncodeToString([]byte(authToken)) } // NewSystemInstanceForClient - creates a new SystemInstance func NewSystemInstanceForClient(clientInstance *ff.Fortify, httpClientInstance *piperHttp.Client, serverURL, authToken string, requestTimeout time.Duration) *SystemInstance { return &SystemInstance{ timeout: requestTimeout, token: authToken, serverURL: serverURL, client: clientInstance, httpClient: httpClientInstance, logger: log.Entry().WithField("package", "SAP/jenkins-library/pkg/fortify"), } } // AuthenticateRequest authenticates the request func (sys *SystemInstance) AuthenticateRequest(req runtime.ClientRequest, formats strfmt.Registry) error { req.SetHeaderParam("Authorization", fmt.Sprintf("FortifyToken %v", sys.token)) return nil } // GetProjectByName returns the project identified by the name provided // autoCreate and projectVersion parameters only used if autoCreate=true func (sys *SystemInstance) GetProjectByName(projectName string, autoCreate bool, projectVersionName string) (*models.Project, error) { nameParam := fmt.Sprintf(`name:"%v"`, projectName) params := &project_controller.ListProjectParams{Q: &nameParam} params.WithTimeout(sys.timeout) result, err := sys.client.ProjectController.ListProject(params, sys) if err != nil { return nil, fmt.Errorf("Error from url %s %w", sys.serverURL, err) } for _, project := range result.GetPayload().Data { if *project.Name == projectName { return project, nil } } // Project with specified name was NOT found, check if autoCreate flag is set, if not stop otherwise create it automatically if !autoCreate { log.SetErrorCategory(log.ErrorConfiguration) return nil, fmt.Errorf("Project with name %v not found in backend and automatic creation not enabled", projectName) } log.Entry().Debugf("No projects found with name: %v auto-creating one now...", projectName) projectVersion, err := sys.CreateProjectVersionIfNotExist(projectName, projectVersionName, "Created by Go script") if err != nil { return nil, fmt.Errorf("failed to auto-create new project: %w", err) } log.Entry().Debugf("Finished creating project: %v", projectVersion) return projectVersion.Project, nil } // GetProjectVersionDetailsByProjectIDAndVersionName returns the project version details of the project version identified by the id and project versionname // projectName parameter is only used if autoCreate=true func (sys *SystemInstance) GetProjectVersionDetailsByProjectIDAndVersionName(id int64, versionName string, autoCreate bool, projectName string) (*models.ProjectVersion, error) { nameParam := fmt.Sprintf(`name:"%v"`, versionName) params := &project_version_of_project_controller.ListProjectVersionOfProjectParams{ParentID: id, Q: &nameParam} params.WithTimeout(sys.timeout) result, err := sys.client.ProjectVersionOfProjectController.ListProjectVersionOfProject(params, sys) if err != nil { return nil, fmt.Errorf("Error from url %s %w", sys.serverURL, err) } if result.Payload.Count > 0 { projectVersion := result.GetPayload().Data[0] return projectVersion, nil } // projectVersion not found for specified project id and name, check if autoCreate is enabled if !autoCreate { log.SetErrorCategory(log.ErrorConfiguration) return nil, errors.New(fmt.Sprintf("Project version with name %v not found in project with ID %v and automatic creation not enabled", versionName, id)) } log.Entry().Debugf("Could not find project version with name %v under project %v auto-creating one now...", versionName, projectName) version, err := sys.CreateProjectVersionIfNotExist(projectName, versionName, "Created by Go script") if err != nil { return nil, errors.Wrapf(err, "failed to auto-create project version: %v for project %v", versionName, projectName) } log.Entry().Debugf("Successfully created project version %v for project %v", versionName, projectName) return version, nil } // CreateProjectVersionIfNotExist creates a new ProjectVersion if it does not already exist. // If the projectName also does not exist, it will create that as well. func (sys *SystemInstance) CreateProjectVersionIfNotExist(projectName, projectVersionName, description string) (*models.ProjectVersion, error) { var projectID int64 = 0 // check if project with projectName exists projectResp, err := sys.GetProjectByName(projectName, false, "") if err == nil { // project already exists, all we need to do is append a new ProjectVersion to it // save the project id for later projectID = projectResp.ID } issueTemplateID := "4c5799c9-1940-4abe-b57a-3bcad88eb041" active := true committed := true projectVersionDto := &models.ProjectVersion{ Name: &projectVersionName, Description: &description, IssueTemplateID: &issueTemplateID, Active: &active, Committed: &committed, Project: &models.Project{ID: projectID}, } if projectVersionDto.Project.ID == 0 { // project does not exist, set one up projectVersionDto.Project = &models.Project{ Name: &projectName, Description: description, IssueTemplateID: &issueTemplateID, } } projectVersion, err := sys.CreateProjectVersion(projectVersionDto) if err != nil { return nil, errors.Wrapf(err, "Failed to create new project version %v for projectName %v", projectVersionName, projectName) } _, err = sys.CommitProjectVersion(projectVersion.ID) if err != nil { return nil, errors.Wrapf(err, "Failed to commit project version %v: %v", projectVersion.ID, err) } return projectVersion, nil } // LookupOrCreateProjectVersionDetailsForPullRequest looks up a project version for pull requests or creates it from scratch func (sys *SystemInstance) LookupOrCreateProjectVersionDetailsForPullRequest(projectID int64, masterProjectVersion *models.ProjectVersion, pullRequestName string) (*models.ProjectVersion, error) { projectVersion, _ := sys.GetProjectVersionDetailsByProjectIDAndVersionName(projectID, pullRequestName, false, "") if nil != projectVersion { return projectVersion, nil } newVersion := &models.ProjectVersion{} newVersion.Name = &pullRequestName newVersion.Description = masterProjectVersion.Description newVersion.Active = masterProjectVersion.Active newVersion.Committed = masterProjectVersion.Committed newVersion.Project = &models.Project{} newVersion.Project.Name = masterProjectVersion.Project.Name newVersion.Project.Description = masterProjectVersion.Project.Description newVersion.Project.ID = masterProjectVersion.Project.ID newVersion.IssueTemplateID = masterProjectVersion.IssueTemplateID projectVersion, err := sys.CreateProjectVersion(newVersion) if err != nil { return nil, errors.Wrapf(err, "Failed to create new project version for pull request %v", pullRequestName) } attributes, err := sys.GetProjectVersionAttributesByProjectVersionID(masterProjectVersion.ID) if err != nil { return nil, errors.Wrapf(err, "Failed to load project version attributes for master project version %v", masterProjectVersion.ID) } for _, attribute := range attributes { attribute.ID = 0 } _, err = sys.SetProjectVersionAttributesByProjectVersionID(projectVersion.ID, attributes) if err != nil { return nil, errors.Wrapf(err, "Failed to update project version attributes for pull request project version %v", projectVersion.ID) } err = sys.ProjectVersionCopyFromPartial(masterProjectVersion.ID, projectVersion.ID) if err != nil { return nil, errors.Wrapf(err, "Failed to copy from partial of project version %v to %v", masterProjectVersion.ID, projectVersion.ID) } _, err = sys.CommitProjectVersion(projectVersion.ID) if err != nil { return nil, errors.Wrapf(err, "Failed to commit project version %v: %v", projectVersion.ID, err) } err = sys.ProjectVersionCopyCurrentState(masterProjectVersion.ID, projectVersion.ID) if err != nil { return nil, errors.Wrapf(err, "Failed to copy current state of project version %v to %v", masterProjectVersion.ID, projectVersion.ID) } err = sys.ProjectVersionCopyPermissions(masterProjectVersion.ID, projectVersion.ID) if err != nil { return nil, errors.Wrapf(err, "Failed to copy permissions of project version %v to %v", masterProjectVersion.ID, projectVersion.ID) } return projectVersion, nil } // GetProjectVersionAttributesByProjectVersionID returns the project version attributes of the project version identified by the id func (sys *SystemInstance) GetProjectVersionAttributesByProjectVersionID(id int64) ([]*models.Attribute, error) { params := &attribute_of_project_version_controller.ListAttributeOfProjectVersionParams{ParentID: id} params.WithTimeout(sys.timeout) result, err := sys.client.AttributeOfProjectVersionController.ListAttributeOfProjectVersion(params, sys) if err != nil { return nil, fmt.Errorf("Error from url %s %w", sys.serverURL, err) } return result.GetPayload().Data, nil } // SetProjectVersionAttributesByProjectVersionID sets the project version attributes of the project version identified by the id func (sys *SystemInstance) SetProjectVersionAttributesByProjectVersionID(id int64, attributes []*models.Attribute) ([]*models.Attribute, error) { params := &attribute_of_project_version_controller.UpdateCollectionAttributeOfProjectVersionParams{ParentID: id, Data: attributes} params.WithTimeout(sys.timeout) result, err := sys.client.AttributeOfProjectVersionController.UpdateCollectionAttributeOfProjectVersion(params, sys) if err != nil { return nil, fmt.Errorf("Error from url %s %w", sys.serverURL, err) } return result.GetPayload().Data, nil } // CreateProjectVersion creates the project version with the provided details func (sys *SystemInstance) CreateProjectVersion(version *models.ProjectVersion) (*models.ProjectVersion, error) { params := &project_version_controller.CreateProjectVersionParams{Resource: version} params.WithTimeout(sys.timeout) result, err := sys.client.ProjectVersionController.CreateProjectVersion(params, sys) if err != nil { return nil, fmt.Errorf("Error from url %s %w", sys.serverURL, err) } return result.GetPayload().Data, nil } // ProjectVersionCopyFromPartial copies parts of the source project version to the target project version identified by their ids func (sys *SystemInstance) ProjectVersionCopyFromPartial(sourceID, targetID int64) error { enable := true settings := models.ProjectVersionCopyPartialRequest{ ProjectVersionID: &targetID, PreviousProjectVersionID: &sourceID, CopyAnalysisProcessingRules: &enable, CopyBugTrackerConfiguration: &enable, CopyCustomTags: &enable, } params := &project_version_controller.CopyProjectVersionParams{Resource: &settings} params.WithTimeout(sys.timeout) _, err := sys.client.ProjectVersionController.CopyProjectVersion(params, sys) if err != nil { return fmt.Errorf("Error from url %s %w", sys.serverURL, err) } return nil } // ProjectVersionCopyCurrentState copies the project version state of sourceID into the new project version addressed by targetID func (sys *SystemInstance) ProjectVersionCopyCurrentState(sourceID, targetID int64) error { settings := models.ProjectVersionCopyCurrentStateRequest{ ProjectVersionID: &targetID, PreviousProjectVersionID: &sourceID, } params := &project_version_controller.CopyCurrentStateForProjectVersionParams{Resource: &settings} params.WithTimeout(sys.timeout) _, err := sys.client.ProjectVersionController.CopyCurrentStateForProjectVersion(params, sys) if err != nil { return fmt.Errorf("Error from url %s %w", sys.serverURL, err) } return nil } func (sys *SystemInstance) getAuthEntityOfProjectVersion(id int64) ([]*models.AuthenticationEntity, error) { embed := "roles" params := &auth_entity_of_project_version_controller.ListAuthEntityOfProjectVersionParams{Embed: &embed, ParentID: id} params.WithTimeout(sys.timeout) result, err := sys.client.AuthEntityOfProjectVersionController.ListAuthEntityOfProjectVersion(params, sys) if err != nil { return nil, fmt.Errorf("Error from url %s %w", sys.serverURL, err) } return result.GetPayload().Data, nil } func (sys *SystemInstance) updateCollectionAuthEntityOfProjectVersion(id int64, data []*models.AuthenticationEntity) error { params := &auth_entity_of_project_version_controller.UpdateCollectionAuthEntityOfProjectVersionParams{ParentID: id, Data: data} params.WithTimeout(sys.timeout) _, err := sys.client.AuthEntityOfProjectVersionController.UpdateCollectionAuthEntityOfProjectVersion(params, sys) if err != nil { return fmt.Errorf("Error from url %s %w", sys.serverURL, err) } return nil } // ProjectVersionCopyPermissions copies the authentication entity of the project version addressed by sourceID to the one of targetID func (sys *SystemInstance) ProjectVersionCopyPermissions(sourceID, targetID int64) error { result, err := sys.getAuthEntityOfProjectVersion(sourceID) if err != nil { return err } err = sys.updateCollectionAuthEntityOfProjectVersion(targetID, result) if err != nil { return err } return nil } func (sys *SystemInstance) updateProjectVersionDetails(id int64, details *models.ProjectVersion) (*models.ProjectVersion, error) { params := &project_version_controller.UpdateProjectVersionParams{ID: id, Resource: details} params.WithTimeout(sys.timeout) result, err := sys.client.ProjectVersionController.UpdateProjectVersion(params, sys) if err != nil { return nil, fmt.Errorf("Error from url %s %w", sys.serverURL, err) } return result.GetPayload().Data, nil } // CommitProjectVersion commits the project version with the provided id func (sys *SystemInstance) CommitProjectVersion(id int64) (*models.ProjectVersion, error) { enabled := true update := models.ProjectVersion{Committed: &enabled} return sys.updateProjectVersionDetails(id, &update) } func (sys *SystemInstance) inactivateProjectVersion(id int64) (*models.ProjectVersion, error) { enabled := true disabled := false update := models.ProjectVersion{Committed: &enabled, Active: &disabled} return sys.updateProjectVersionDetails(id, &update) } // GetArtifactsOfProjectVersion returns the list of artifacts related to the project version addressed with id func (sys *SystemInstance) GetArtifactsOfProjectVersion(id int64) ([]*models.Artifact, error) { scans := "scans" params := &artifact_of_project_version_controller.ListArtifactOfProjectVersionParams{ParentID: id, Embed: &scans} params.WithTimeout(sys.timeout) result, err := sys.client.ArtifactOfProjectVersionController.ListArtifactOfProjectVersion(params, sys) if err != nil { return nil, fmt.Errorf("Error from url %s %w", sys.serverURL, err) } return result.GetPayload().Data, nil } // MergeProjectVersionStateOfPRIntoMaster merges the PR project version's fpr result file into the master project version func (sys *SystemInstance) MergeProjectVersionStateOfPRIntoMaster(downloadEndpoint, uploadEndpoint string, masterProjectID, masterProjectVersionID int64, pullRequestName string) error { log.Entry().Debugf("Looking up project version with name '%v' to merge audit status into master version", pullRequestName) prProjectVersion, _ := sys.GetProjectVersionDetailsByProjectIDAndVersionName(masterProjectID, pullRequestName, false, "") if nil != prProjectVersion { log.Entry().Debugf("Found project version with ID '%v', starting transfer", prProjectVersion.ID) data, err := sys.DownloadResultFile(downloadEndpoint, prProjectVersion.ID) if err != nil { return errors.Wrapf(err, "Failed to download current state FPR of PR project version %v", prProjectVersion.ID) } err = sys.uploadResultFileContent(uploadEndpoint, "prMergeTransfer.fpr", bytes.NewReader(data), masterProjectVersionID) if err != nil { return errors.Wrapf(err, "Failed to upload PR project version state to master project version %v", masterProjectVersionID) } _, err = sys.inactivateProjectVersion(prProjectVersion.ID) if err != nil { log.Entry().Warnf("Failed to inactivate merged PR project version %v", prProjectVersion.ID) } } else { log.Entry().Debug("No related project version found in SSC") } return nil } // GetFilterSetOfProjectVersionByTitle returns the filter set with the given title related to the project version addressed with id, if no title is provided the default filter set will be returned func (sys *SystemInstance) GetFilterSetOfProjectVersionByTitle(id int64, title string) (*models.FilterSet, error) { params := &filter_set_of_project_version_controller.ListFilterSetOfProjectVersionParams{ParentID: id} params.WithTimeout(sys.timeout) result, err := sys.client.FilterSetOfProjectVersionController.ListFilterSetOfProjectVersion(params, sys) if err != nil { return nil, fmt.Errorf("Error from url %s %w", sys.serverURL, err) } var defaultFilterSet *models.FilterSet for _, filterSet := range result.GetPayload().Data { if len(title) > 0 && filterSet.Title == title { return filterSet, nil } if filterSet.DefaultFilterSet { defaultFilterSet = filterSet } } if len(title) > 0 { log.Entry().Warnf("Failed to load filter set with title '%v', falling back to default filter set", title) } if nil != defaultFilterSet { return defaultFilterSet, nil } return nil, fmt.Errorf("Failed to identify requested filter set and default filter") } // GetIssueFilterSelectorOfProjectVersionByName returns the groupings with the given names related to the project version addressed with id func (sys *SystemInstance) GetIssueFilterSelectorOfProjectVersionByName(id int64, names []string, options []string) (*models.IssueFilterSelectorSet, error) { params := &issue_selector_set_of_project_version_controller.GetIssueSelectorSetOfProjectVersionParams{ParentID: id} params.WithTimeout(sys.timeout) result, err := sys.client.IssueSelectorSetOfProjectVersionController.GetIssueSelectorSetOfProjectVersion(params, sys) if err != nil { return nil, fmt.Errorf("Error from url %s %w", sys.serverURL, err) } return sys.ReduceIssueFilterSelectorSet(result.GetPayload().Data, names, options), nil } // ReduceIssueFilterSelectorSet filters the set to the relevant filter display names func (sys *SystemInstance) ReduceIssueFilterSelectorSet(issueFilterSelectorSet *models.IssueFilterSelectorSet, names []string, options []string) *models.IssueFilterSelectorSet { groupingList := []*models.IssueSelector{} if issueFilterSelectorSet.GroupBySet != nil { for _, group := range issueFilterSelectorSet.GroupBySet { if piperutils.ContainsString(names, *group.DisplayName) { log.Entry().Debugf("adding new grouping '%v' to reduced list", *group.DisplayName) groupingList = append(groupingList, group) } } } filterList := []*models.IssueFilterSelector{} if issueFilterSelectorSet.FilterBySet != nil { for _, filter := range issueFilterSelectorSet.FilterBySet { if piperutils.ContainsString(names, filter.DisplayName) { newFilter := &models.IssueFilterSelector{} newFilter.DisplayName = filter.DisplayName newFilter.Description = filter.Description newFilter.EntityType = filter.EntityType newFilter.FilterSelectorType = filter.FilterSelectorType newFilter.GUID = filter.GUID newFilter.Value = filter.Value newFilter.SelectorOptions = []*models.SelectorOption{} for _, option := range filter.SelectorOptions { if (nil != options && piperutils.ContainsString(options, option.DisplayName)) || options == nil || len(options) == 0 { log.Entry().Debugf("adding selector option '%v' to list for filter selector '%v'", option.DisplayName, newFilter.DisplayName) newFilter.SelectorOptions = append(newFilter.SelectorOptions, option) } } log.Entry().Debugf("adding new filter '%v' to reduced list with selector options '%v'", newFilter.DisplayName, newFilter.SelectorOptions) filterList = append(filterList, newFilter) } } } return &models.IssueFilterSelectorSet{GroupBySet: groupingList, FilterBySet: filterList} } // GetFilterSetByDisplayName returns the set identified by the provided name or nil func (sys *SystemInstance) GetFilterSetByDisplayName(issueFilterSelectorSet *models.IssueFilterSelectorSet, name string) *models.IssueFilterSelector { if issueFilterSelectorSet.FilterBySet != nil { for _, filter := range issueFilterSelectorSet.FilterBySet { if filter.DisplayName == name { return filter } } } return nil } func (sys *SystemInstance) getIssuesOfProjectVersion(id int64, filter, filterset, groupingtype string) ([]*models.ProjectVersionIssueGroup, error) { enable := true params := &issue_group_of_project_version_controller.ListIssueGroupOfProjectVersionParams{ParentID: id, Showsuppressed: &enable, Filterset: &filterset, Groupingtype: &groupingtype} params.WithTimeout(sys.timeout) if len(filter) > 0 { params.WithFilter(&filter) } result, err := sys.client.IssueGroupOfProjectVersionController.ListIssueGroupOfProjectVersion(params, sys) if err != nil { return nil, fmt.Errorf("Error from url %s %w", sys.serverURL, err) } return result.GetPayload().Data, nil } // GetProjectIssuesByIDAndFilterSetGroupedBySelector returns issues of the project version addressed with id filtered with the respective set and grouped by the issue filter selector grouping func (sys *SystemInstance) GetProjectIssuesByIDAndFilterSetGroupedBySelector(id int64, filter, filterSetGUID string, issueFilterSelectorSet *models.IssueFilterSelectorSet) ([]*models.ProjectVersionIssueGroup, error) { groupingTypeGUID := "" if issueFilterSelectorSet != nil { groupingTypeGUID = *issueFilterSelectorSet.GroupBySet[0].GUID } result, err := sys.getIssuesOfProjectVersion(id, filter, filterSetGUID, groupingTypeGUID) if err != nil { return nil, err } return result, nil } // GetIssueStatisticsOfProjectVersion returns the issue statistics related to the project version addressed with id func (sys *SystemInstance) GetIssueStatisticsOfProjectVersion(id int64) ([]*models.IssueStatistics, error) { params := &issue_statistics_of_project_version_controller.ListIssueStatisticsOfProjectVersionParams{ParentID: id} params.WithTimeout(sys.timeout) result, err := sys.client.IssueStatisticsOfProjectVersionController.ListIssueStatisticsOfProjectVersion(params, sys) if err != nil { return nil, fmt.Errorf("Error from url %s %w", sys.serverURL, err) } return result.GetPayload().Data, nil } // GenerateQGateReport returns the issue statistics related to the project version addressed with id func (sys *SystemInstance) GenerateQGateReport(projectID, projectVersionID, reportTemplateID int64, projectName, projectVersionName, reportFormat string) (*models.SavedReport, error) { paramIdentifier := "projectVersionId" paramType := "SINGLE_PROJECT" paramName := "Q-gate-report" reportType := "PORTFOLIO" inputReportParameters := []*models.InputReportParameter{{Name: ¶mName, Identifier: ¶mIdentifier, ParamValue: projectVersionID, Type: ¶mType}} reportProjectVersions := []*models.ReportProjectVersion{{ID: projectVersionID, Name: projectVersionName}} reportProjects := []*models.ReportProject{{ID: projectID, Name: projectName, Versions: reportProjectVersions}} report := models.SavedReport{Name: fmt.Sprintf("FortifyReport: %v:%v", projectName, projectVersionName), Type: &reportType, ReportDefinitionID: &reportTemplateID, Format: &reportFormat, Projects: reportProjects, InputReportParameters: inputReportParameters} params := &saved_report_controller.CreateSavedReportParams{Resource: &report} params.WithTimeout(sys.timeout) result, err := sys.client.SavedReportController.CreateSavedReport(params, sys) if err != nil { return nil, fmt.Errorf("Error from url %s %w", sys.serverURL, err) } return result.GetPayload().Data, nil } // GetReportDetails returns the details of the report addressed with id func (sys *SystemInstance) GetReportDetails(id int64) (*models.SavedReport, error) { params := &saved_report_controller.ReadSavedReportParams{ID: id} params.WithTimeout(sys.timeout) result, err := sys.client.SavedReportController.ReadSavedReport(params, sys) if err != nil { return nil, fmt.Errorf("Error from url %s %w", sys.serverURL, err) } return result.GetPayload().Data, nil } // GetIssueDetails returns the details of an issue with its issueInstanceId and projectVersionId func (sys *SystemInstance) GetIssueDetails(projectVersionId int64, issueInstanceId string) ([]*models.ProjectVersionIssue, error) { qmStr := "issues" showSuppressed := true params := &issue_of_project_version_controller.ListIssueOfProjectVersionParams{ParentID: projectVersionId, Q: &issueInstanceId, Qm: &qmStr, Showsuppressed: &showSuppressed} params.WithTimeout(sys.timeout) result, err := sys.client.IssueOfProjectVersionController.ListIssueOfProjectVersion(params, sys) if err != nil { return nil, fmt.Errorf("Error from url %s %w", sys.serverURL, err) } return result.GetPayload().Data, nil } // GetAllIssueDetails returns the details of all issues of the project with id projectVersionId func (sys *SystemInstance) GetAllIssueDetails(projectVersionId int64) ([]*models.ProjectVersionIssue, error) { var limit int32 limit = -1 showSuppressed := true params := &issue_of_project_version_controller.ListIssueOfProjectVersionParams{ParentID: projectVersionId, Limit: &limit, Showsuppressed: &showSuppressed} params.WithTimeout(sys.timeout) result, err := sys.client.IssueOfProjectVersionController.ListIssueOfProjectVersion(params, sys) if err != nil { return nil, fmt.Errorf("Error from url %s %w", sys.serverURL, err) } return result.GetPayload().Data, nil } // GetIssueComments returns the details of an issue comments with its unique parentId func (sys *SystemInstance) GetIssueComments(parentId int64) ([]*models.IssueAuditComment, error) { params := &issue_audit_comment_of_issue_controller.ListIssueAuditCommentOfIssueParams{ParentID: parentId} params.WithTimeout(sys.timeout) result, err := sys.client.IssueAuditCommentOfIssueController.ListIssueAuditCommentOfIssue(params, sys) if err != nil { return nil, fmt.Errorf("Error from url %s %w", sys.serverURL, err) } return result.GetPayload().Data, nil } func (sys *SystemInstance) invalidateFileTokens() error { log.Entry().Debug("invalidating file tokens") params := &file_token_controller.MultiDeleteFileTokenParams{} params.WithTimeout(sys.timeout) _, err := sys.client.FileTokenController.MultiDeleteFileToken(params, sys) if err != nil { return fmt.Errorf("Error from url %s %w", sys.serverURL, err) } return nil } func (sys *SystemInstance) getFileToken(tokenType string) (*models.FileToken, error) { log.Entry().Debugf("fetching file token of type %v", tokenType) token := models.FileToken{FileTokenType: &tokenType} params := &file_token_controller.CreateFileTokenParams{Resource: &token} params.WithTimeout(sys.timeout) result, err := sys.client.FileTokenController.CreateFileToken(params, sys) if err != nil { return nil, fmt.Errorf("Error from url %s %w", sys.serverURL, err) } return result.GetPayload().Data, nil } // UploadResultFile uploads a fpr file to the fortify backend func (sys *SystemInstance) UploadResultFile(endpoint, file string, projectVersionID int64) error { fileHandle, err := os.Open(file) if err != nil { return errors.Wrapf(err, "Unable to locate file %v", file) } defer fileHandle.Close() return sys.uploadResultFileContent(endpoint, file, fileHandle, projectVersionID) } func (sys *SystemInstance) uploadResultFileContent(endpoint, file string, fileContent io.Reader, projectVersionID int64) error { token, err := sys.getFileToken("UPLOAD") if err != nil { return err } defer sys.invalidateFileTokens() header := http.Header{} header.Add("Cache-Control", "no-cache, no-store, must-revalidate") header.Add("Pragma", "no-cache") formFields := map[string]string{} formFields["entityId"] = fmt.Sprintf("%v", projectVersionID) _, err = sys.httpClient.Upload(piperHttp.UploadRequestData{ Method: http.MethodPost, URL: fmt.Sprintf("%v%v?mat=%v", sys.serverURL, endpoint, token.Token), File: file, FileFieldName: "file", FormFields: formFields, FileContent: fileContent, Header: header, }) return err } // DownloadFile downloads a file from Fortify backend func (sys *SystemInstance) downloadFile(endpoint, method, acceptType, tokenType string, fileID int64) ([]byte, error) { token, err := sys.getFileToken(tokenType) if err != nil { return nil, errors.Wrap(err, "Error fetching file token") } defer sys.invalidateFileTokens() header := http.Header{} header.Add("Cache-Control", "no-cache, no-store, must-revalidate") header.Add("Pragma", "no-cache") header.Add("Accept", acceptType) header.Add("Content-Type", "application/form-data") body := url.Values{ "id": {fmt.Sprintf("%v", fileID)}, "mat": {token.Token}, } var response *http.Response if method == http.MethodGet { response, err = sys.httpClient.SendRequest(method, fmt.Sprintf("%v%v?%v", sys.serverURL, endpoint, body.Encode()), nil, header, nil) } else { response, err = sys.httpClient.SendRequest(method, fmt.Sprintf("%v%v", sys.serverURL, endpoint), strings.NewReader(body.Encode()), header, nil) } if err != nil { return nil, err } data, err := io.ReadAll(response.Body) defer response.Body.Close() if err != nil { return nil, errors.Wrap(err, "Error reading the response data") } return data, nil } // DownloadReportFile downloads a report file from Fortify backend func (sys *SystemInstance) DownloadReportFile(endpoint string, reportID int64) ([]byte, error) { data, err := sys.downloadFile(endpoint, http.MethodGet, "application/octet-stream", "REPORT_FILE", reportID) if err != nil { return nil, errors.Wrap(err, "Error downloading report file") } return data, nil } // DownloadResultFile downloads a result file from Fortify backend func (sys *SystemInstance) DownloadResultFile(endpoint string, projectVersionID int64) ([]byte, error) { data, err := sys.downloadFile(endpoint, http.MethodGet, "application/zip", "DOWNLOAD", projectVersionID) if err != nil { return nil, errors.Wrap(err, "Error downloading result file") } return data, nil }