mirror of
https://github.com/projectdiscovery/nuclei.git
synced 2025-12-17 17:35:28 +00:00
* (feat) add Gitea reporting platform support * (fix) remove debugging code * (chore) fix typo * (chore) update syntax with recent changes * (fix) use Index to update issue comments * (feat) add gitea config example * added missing option --------- Co-authored-by: sandeep <8293321+ehsandeep@users.noreply.github.com>
177 lines
4.6 KiB
Go
177 lines
4.6 KiB
Go
package gitea
|
|
|
|
import (
|
|
"fmt"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"code.gitea.io/sdk/gitea"
|
|
"github.com/pkg/errors"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/output"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/markdown/util"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/format"
|
|
"github.com/projectdiscovery/retryablehttp-go"
|
|
)
|
|
|
|
// Integration is a client for an issue tracker integration
|
|
type Integration struct {
|
|
client *gitea.Client
|
|
options *Options
|
|
}
|
|
|
|
// Options contains the configuration options for gitea issue tracker client
|
|
type Options struct {
|
|
// BaseURL (optional) is the self-hosted Gitea application url
|
|
BaseURL string `yaml:"base-url" validate:"omitempty,url"`
|
|
// Token is the token for gitea account.
|
|
Token string `yaml:"token" validate:"required"`
|
|
// ProjectOwner is the owner (user or org) of the repository.
|
|
ProjectOwner string `yaml:"project-owner" validate:"required"`
|
|
// ProjectName is the name of the repository.
|
|
ProjectName string `yaml:"project-name" validate:"required"`
|
|
// IssueLabel is the label of the created issue type
|
|
IssueLabel string `yaml:"issue-label"`
|
|
// SeverityAsLabel (optional) adds the severity as the label of the created
|
|
// issue.
|
|
SeverityAsLabel bool `yaml:"severity-as-label"`
|
|
// DuplicateIssueCheck is a bool to enable duplicate tracking issue check and update the newest
|
|
DuplicateIssueCheck bool `yaml:"duplicate-issue-check" default:"false"`
|
|
|
|
HttpClient *retryablehttp.Client `yaml:"-"`
|
|
OmitRaw bool `yaml:"-"`
|
|
}
|
|
|
|
// New creates a new issue tracker integration client based on options.
|
|
func New(options *Options) (*Integration, error) {
|
|
|
|
var opts []gitea.ClientOption
|
|
opts = append(opts, gitea.SetToken(options.Token))
|
|
|
|
if options.HttpClient != nil {
|
|
opts = append(opts, gitea.SetHTTPClient(options.HttpClient.HTTPClient))
|
|
}
|
|
|
|
var remote string
|
|
if options.BaseURL != "" {
|
|
parsed, err := url.Parse(options.BaseURL)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "could not parse custom baseurl")
|
|
}
|
|
if !strings.HasSuffix(parsed.Path, "/") {
|
|
parsed.Path += "/"
|
|
}
|
|
remote = parsed.String()
|
|
} else {
|
|
remote = `https://gitea.com/`
|
|
}
|
|
|
|
git, err := gitea.NewClient(remote, opts...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &Integration{client: git, options: options}, nil
|
|
}
|
|
|
|
// CreateIssue creates an issue in the tracker
|
|
func (i *Integration) CreateIssue(event *output.ResultEvent) error {
|
|
summary := format.Summary(event)
|
|
description := format.CreateReportDescription(event, util.MarkdownFormatter{}, i.options.OmitRaw)
|
|
|
|
labels := []string{}
|
|
severityLabel := fmt.Sprintf("Severity: %s", event.Info.SeverityHolder.Severity.String())
|
|
if i.options.SeverityAsLabel && severityLabel != "" {
|
|
labels = append(labels, severityLabel)
|
|
}
|
|
if label := i.options.IssueLabel; label != "" {
|
|
labels = append(labels, label)
|
|
}
|
|
customLabels, err := i.getLabelIDsByNames(labels)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var issue *gitea.Issue
|
|
if i.options.DuplicateIssueCheck {
|
|
issue, err = i.findIssueByTitle(summary)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if issue == nil {
|
|
_, _, err = i.client.CreateIssue(i.options.ProjectOwner, i.options.ProjectName, gitea.CreateIssueOption{
|
|
Title: summary,
|
|
Body: description,
|
|
Labels: customLabels,
|
|
})
|
|
|
|
return err
|
|
}
|
|
|
|
_, _, err = i.client.CreateIssueComment(i.options.ProjectOwner, i.options.ProjectName, issue.Index, gitea.CreateIssueCommentOption{
|
|
Body: description,
|
|
})
|
|
|
|
return err
|
|
}
|
|
|
|
func (i *Integration) findIssueByTitle(title string) (*gitea.Issue, error) {
|
|
|
|
issueList, _, err := i.client.ListRepoIssues(i.options.ProjectOwner, i.options.ProjectName, gitea.ListIssueOption{
|
|
State: "all",
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, issue := range issueList {
|
|
if issue.Title == title {
|
|
return issue, nil
|
|
}
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
func (i *Integration) getLabelIDsByNames(labels []string) ([]int64, error) {
|
|
|
|
var ids []int64
|
|
|
|
existingLabels, _, err := i.client.ListRepoLabels(i.options.ProjectOwner, i.options.ProjectName, gitea.ListLabelsOptions{
|
|
ListOptions: gitea.ListOptions{Page: -1},
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
getLabel := func(name string) int64 {
|
|
for _, existingLabel := range existingLabels {
|
|
if existingLabel.Name == name {
|
|
return existingLabel.ID
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
for _, label := range labels {
|
|
labelID := getLabel(label)
|
|
if labelID == -1 {
|
|
newLabel, _, err := i.client.CreateLabel(i.options.ProjectOwner, i.options.ProjectName, gitea.CreateLabelOption{
|
|
Name: label,
|
|
Color: `#00aabb`,
|
|
Description: label,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ids = append(ids, newLabel.ID)
|
|
} else {
|
|
ids = append(ids, labelID)
|
|
}
|
|
}
|
|
|
|
return ids, nil
|
|
}
|