Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/reporting/trackers/gitea/gitea.go
2070 views
1
package gitea
2
3
import (
4
"fmt"
5
"net/url"
6
"strconv"
7
"strings"
8
9
"code.gitea.io/sdk/gitea"
10
"github.com/pkg/errors"
11
"github.com/projectdiscovery/nuclei/v3/pkg/output"
12
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/markdown/util"
13
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/format"
14
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/filters"
15
"github.com/projectdiscovery/retryablehttp-go"
16
)
17
18
// Integration is a client for an issue tracker integration
19
type Integration struct {
20
client *gitea.Client
21
options *Options
22
}
23
24
// Options contains the configuration options for gitea issue tracker client
25
type Options struct {
26
// BaseURL (optional) is the self-hosted Gitea application url
27
BaseURL string `yaml:"base-url" validate:"omitempty,url"`
28
// Token is the token for gitea account.
29
Token string `yaml:"token" validate:"required"`
30
// ProjectOwner is the owner (user or org) of the repository.
31
ProjectOwner string `yaml:"project-owner" validate:"required"`
32
// ProjectName is the name of the repository.
33
ProjectName string `yaml:"project-name" validate:"required"`
34
// IssueLabel is the label of the created issue type
35
IssueLabel string `yaml:"issue-label"`
36
// SeverityAsLabel (optional) adds the severity as the label of the created
37
// issue.
38
SeverityAsLabel bool `yaml:"severity-as-label"`
39
// AllowList contains a list of allowed events for this tracker
40
AllowList *filters.Filter `yaml:"allow-list"`
41
// DenyList contains a list of denied events for this tracker
42
DenyList *filters.Filter `yaml:"deny-list"`
43
// DuplicateIssueCheck is a bool to enable duplicate tracking issue check and update the newest
44
DuplicateIssueCheck bool `yaml:"duplicate-issue-check" default:"false"`
45
46
HttpClient *retryablehttp.Client `yaml:"-"`
47
OmitRaw bool `yaml:"-"`
48
}
49
50
// New creates a new issue tracker integration client based on options.
51
func New(options *Options) (*Integration, error) {
52
53
var opts []gitea.ClientOption
54
opts = append(opts, gitea.SetToken(options.Token))
55
56
if options.HttpClient != nil {
57
opts = append(opts, gitea.SetHTTPClient(options.HttpClient.HTTPClient))
58
}
59
60
var remote string
61
if options.BaseURL != "" {
62
parsed, err := url.Parse(options.BaseURL)
63
if err != nil {
64
return nil, errors.Wrap(err, "could not parse custom baseurl")
65
}
66
if !strings.HasSuffix(parsed.Path, "/") {
67
parsed.Path += "/"
68
}
69
remote = parsed.String()
70
} else {
71
remote = `https://gitea.com/`
72
}
73
74
git, err := gitea.NewClient(remote, opts...)
75
if err != nil {
76
return nil, err
77
}
78
79
return &Integration{client: git, options: options}, nil
80
}
81
82
// CreateIssue creates an issue in the tracker
83
func (i *Integration) CreateIssue(event *output.ResultEvent) (*filters.CreateIssueResponse, error) {
84
summary := format.Summary(event)
85
description := format.CreateReportDescription(event, util.MarkdownFormatter{}, i.options.OmitRaw)
86
87
labels := []string{}
88
severityLabel := fmt.Sprintf("Severity: %s", event.Info.SeverityHolder.Severity.String())
89
if i.options.SeverityAsLabel && severityLabel != "" {
90
labels = append(labels, severityLabel)
91
}
92
if label := i.options.IssueLabel; label != "" {
93
labels = append(labels, label)
94
}
95
customLabels, err := i.getLabelIDsByNames(labels)
96
if err != nil {
97
return nil, err
98
}
99
100
var issue *gitea.Issue
101
if i.options.DuplicateIssueCheck {
102
issue, err = i.findIssueByTitle(summary)
103
if err != nil {
104
return nil, err
105
}
106
}
107
108
if issue == nil {
109
createdIssue, _, err := i.client.CreateIssue(i.options.ProjectOwner, i.options.ProjectName, gitea.CreateIssueOption{
110
Title: summary,
111
Body: description,
112
Labels: customLabels,
113
})
114
if err != nil {
115
return nil, err
116
}
117
return &filters.CreateIssueResponse{
118
IssueID: strconv.FormatInt(createdIssue.Index, 10),
119
IssueURL: createdIssue.URL,
120
}, nil
121
}
122
123
_, _, err = i.client.CreateIssueComment(i.options.ProjectOwner, i.options.ProjectName, issue.Index, gitea.CreateIssueCommentOption{
124
Body: description,
125
})
126
if err != nil {
127
return nil, err
128
}
129
return &filters.CreateIssueResponse{
130
IssueID: strconv.FormatInt(issue.Index, 10),
131
IssueURL: issue.URL,
132
}, nil
133
}
134
135
func (i *Integration) CloseIssue(event *output.ResultEvent) error {
136
// TODO: Implement
137
return nil
138
}
139
140
// ShouldFilter determines if an issue should be logged to this tracker
141
func (i *Integration) ShouldFilter(event *output.ResultEvent) bool {
142
if i.options.AllowList != nil && !i.options.AllowList.GetMatch(event) {
143
return false
144
}
145
146
if i.options.DenyList != nil && i.options.DenyList.GetMatch(event) {
147
return false
148
}
149
150
return true
151
}
152
153
func (i *Integration) findIssueByTitle(title string) (*gitea.Issue, error) {
154
155
issueList, _, err := i.client.ListRepoIssues(i.options.ProjectOwner, i.options.ProjectName, gitea.ListIssueOption{
156
State: "all",
157
})
158
if err != nil {
159
return nil, err
160
}
161
162
for _, issue := range issueList {
163
if issue.Title == title {
164
return issue, nil
165
}
166
}
167
168
return nil, nil
169
}
170
171
func (i *Integration) getLabelIDsByNames(labels []string) ([]int64, error) {
172
173
var ids []int64
174
175
existingLabels, _, err := i.client.ListRepoLabels(i.options.ProjectOwner, i.options.ProjectName, gitea.ListLabelsOptions{
176
ListOptions: gitea.ListOptions{Page: -1},
177
})
178
if err != nil {
179
return nil, err
180
}
181
182
getLabel := func(name string) int64 {
183
for _, existingLabel := range existingLabels {
184
if existingLabel.Name == name {
185
return existingLabel.ID
186
}
187
}
188
return -1
189
}
190
191
for _, label := range labels {
192
labelID := getLabel(label)
193
if labelID == -1 {
194
newLabel, _, err := i.client.CreateLabel(i.options.ProjectOwner, i.options.ProjectName, gitea.CreateLabelOption{
195
Name: label,
196
Color: `#00aabb`,
197
Description: label,
198
})
199
if err != nil {
200
return nil, err
201
}
202
203
ids = append(ids, newLabel.ID)
204
} else {
205
ids = append(ids, labelID)
206
}
207
}
208
209
return ids, nil
210
}
211
212
func (i *Integration) Name() string {
213
return "gitea"
214
}
215
216