Skip to content

Commit 2d4459d

Browse files
authored
Merge pull request #6712 from projectdiscovery/dwisiswant0/fix/trackers/add-gitlab-paginated-dup-issue-search
fix(trackers): add gitlab paginated dup issue search
2 parents a7df697 + 39a07ca commit 2d4459d

File tree

2 files changed

+77
-33
lines changed

2 files changed

+77
-33
lines changed

cmd/nuclei/issue-tracker-config.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@
5555
# # these severity labels or tags (does not affect exporters. set those globally)
5656
# deny-list:
5757
# severity: low
58+
# # duplicate-issue-check (optional) flag to enable duplicate tracking issue check
59+
# duplicate-issue-check: false
60+
# # duplicate-issue-page-size (optional) controls how many issues to fetch per page when searching for duplicates
61+
# duplicate-issue-page-size: 100
62+
# # duplicate-issue-max-pages (optional) limits how many pages to fetch when searching for duplicates (0 = no limit)
63+
# duplicate-issue-max-pages: 0
5864
#
5965
# Gitea contains configuration options for a gitea issue tracker
6066
#gitea:

pkg/reporting/trackers/gitlab/gitlab.go

Lines changed: 71 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ type Options struct {
4141
DenyList *filters.Filter `yaml:"deny-list"`
4242
// DuplicateIssueCheck is a bool to enable duplicate tracking issue check and update the newest
4343
DuplicateIssueCheck bool `yaml:"duplicate-issue-check" default:"false"`
44+
// DuplicateIssuePageSize controls how many issues are fetched per page when searching for duplicates.
45+
// If unset or <=0, a default of 100 is used.
46+
DuplicateIssuePageSize int `yaml:"duplicate-issue-page-size" default:"100"`
47+
// DuplicateIssueMaxPages limits how many pages are fetched when searching for duplicates.
48+
// If unset or <=0, all pages are fetched until exhaustion.
49+
DuplicateIssueMaxPages int `yaml:"duplicate-issue-max-pages" default:"0"`
4450

4551
HttpClient *retryablehttp.Client `yaml:"-"`
4652
OmitRaw bool `yaml:"-"`
@@ -80,39 +86,36 @@ func (i *Integration) CreateIssue(event *output.ResultEvent) (*filters.CreateIss
8086
}
8187
customLabels := gitlab.LabelOptions(labels)
8288
assigneeIDs := []int{i.userID}
89+
90+
var issue *gitlab.Issue
8391
if i.options.DuplicateIssueCheck {
84-
searchIn := "title"
85-
searchState := "all"
86-
issues, _, err := i.client.Issues.ListProjectIssues(i.options.ProjectName, &gitlab.ListProjectIssuesOptions{
87-
In: &searchIn,
88-
State: &searchState,
89-
Search: &summary,
92+
var err error
93+
issue, err = i.findIssueByTitle(summary)
94+
if err != nil {
95+
return nil, err
96+
}
97+
}
98+
99+
if issue != nil {
100+
_, _, err := i.client.Notes.CreateIssueNote(i.options.ProjectName, issue.IID, &gitlab.CreateIssueNoteOptions{
101+
Body: &description,
90102
})
91103
if err != nil {
92104
return nil, err
93105
}
94-
if len(issues) > 0 {
95-
issue := issues[0]
96-
_, _, err := i.client.Notes.CreateIssueNote(i.options.ProjectName, issue.IID, &gitlab.CreateIssueNoteOptions{
97-
Body: &description,
106+
if issue.State == "closed" {
107+
reopen := "reopen"
108+
_, _, err := i.client.Issues.UpdateIssue(i.options.ProjectName, issue.IID, &gitlab.UpdateIssueOptions{
109+
StateEvent: &reopen,
98110
})
99111
if err != nil {
100112
return nil, err
101113
}
102-
if issue.State == "closed" {
103-
reopen := "reopen"
104-
_, _, err := i.client.Issues.UpdateIssue(i.options.ProjectName, issue.IID, &gitlab.UpdateIssueOptions{
105-
StateEvent: &reopen,
106-
})
107-
if err != nil {
108-
return nil, err
109-
}
110-
}
111-
return &filters.CreateIssueResponse{
112-
IssueID: strconv.FormatInt(int64(issue.ID), 10),
113-
IssueURL: issue.WebURL,
114-
}, nil
115114
}
115+
return &filters.CreateIssueResponse{
116+
IssueID: strconv.FormatInt(int64(issue.ID), 10),
117+
IssueURL: issue.WebURL,
118+
}, nil
116119
}
117120
createdIssue, _, err := i.client.Issues.CreateIssue(i.options.ProjectName, &gitlab.CreateIssueOptions{
118121
Title: &summary,
@@ -134,23 +137,15 @@ func (i *Integration) Name() string {
134137
}
135138

136139
func (i *Integration) CloseIssue(event *output.ResultEvent) error {
137-
searchIn := "title"
138-
searchState := "all"
139-
140140
summary := format.Summary(event)
141-
issues, _, err := i.client.Issues.ListProjectIssues(i.options.ProjectName, &gitlab.ListProjectIssuesOptions{
142-
In: &searchIn,
143-
State: &searchState,
144-
Search: &summary,
145-
})
141+
issue, err := i.findIssueByTitle(summary)
146142
if err != nil {
147143
return err
148144
}
149-
if len(issues) <= 0 {
145+
if issue == nil {
150146
return nil
151147
}
152148

153-
issue := issues[0]
154149
state := "close"
155150
_, _, err = i.client.Issues.UpdateIssue(i.options.ProjectName, issue.IID, &gitlab.UpdateIssueOptions{
156151
StateEvent: &state,
@@ -161,6 +156,49 @@ func (i *Integration) CloseIssue(event *output.ResultEvent) error {
161156
return nil
162157
}
163158

159+
func (i *Integration) findIssueByTitle(title string) (*gitlab.Issue, error) {
160+
pageSize := i.options.DuplicateIssuePageSize
161+
if pageSize <= 0 {
162+
pageSize = 100
163+
}
164+
maxPages := i.options.DuplicateIssueMaxPages
165+
166+
searchIn := "title"
167+
searchState := "all"
168+
page := 1
169+
170+
for {
171+
if maxPages > 0 && page > maxPages {
172+
return nil, nil
173+
}
174+
175+
issues, _, err := i.client.Issues.ListProjectIssues(i.options.ProjectName, &gitlab.ListProjectIssuesOptions{
176+
In: &searchIn,
177+
State: &searchState,
178+
Search: &title,
179+
ListOptions: gitlab.ListOptions{
180+
Page: page,
181+
PerPage: pageSize,
182+
},
183+
})
184+
if err != nil {
185+
return nil, err
186+
}
187+
188+
for _, issue := range issues {
189+
if issue.Title == title {
190+
return issue, nil
191+
}
192+
}
193+
194+
if len(issues) < pageSize {
195+
return nil, nil
196+
}
197+
198+
page++
199+
}
200+
}
201+
164202
// ShouldFilter determines if an issue should be logged to this tracker
165203
func (i *Integration) ShouldFilter(event *output.ResultEvent) bool {
166204
if i.options.AllowList != nil && !i.options.AllowList.GetMatch(event) {

0 commit comments

Comments
 (0)