Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/reporting/trackers/jira/jira_test.go
2871 views
1
package jira
2
3
import (
4
"net/http"
5
"os"
6
"strings"
7
"testing"
8
9
"github.com/projectdiscovery/nuclei/v3/pkg/model"
10
"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity"
11
"github.com/projectdiscovery/nuclei/v3/pkg/model/types/stringslice"
12
"github.com/projectdiscovery/nuclei/v3/pkg/output"
13
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/filters"
14
"github.com/projectdiscovery/retryablehttp-go"
15
"github.com/stretchr/testify/require"
16
)
17
18
type recordingTransport struct {
19
inner http.RoundTripper
20
paths []string
21
}
22
23
func (rt *recordingTransport) RoundTrip(req *http.Request) (*http.Response, error) {
24
if rt.inner == nil {
25
rt.inner = http.DefaultTransport
26
}
27
rt.paths = append(rt.paths, req.URL.Path)
28
return rt.inner.RoundTrip(req)
29
}
30
31
func TestLinkCreation(t *testing.T) {
32
jiraIntegration := &Integration{}
33
link := jiraIntegration.CreateLink("ProjectDiscovery", "https://projectdiscovery.io")
34
require.Equal(t, "[ProjectDiscovery|https://projectdiscovery.io]", link)
35
}
36
37
func TestHorizontalLineCreation(t *testing.T) {
38
jiraIntegration := &Integration{}
39
horizontalLine := jiraIntegration.CreateHorizontalLine()
40
require.True(t, strings.Contains(horizontalLine, "----"))
41
}
42
43
func TestTableCreation(t *testing.T) {
44
jiraIntegration := &Integration{}
45
46
table, err := jiraIntegration.CreateTable([]string{"key", "value"}, [][]string{
47
{"a", "b"},
48
{"c"},
49
{"d", "e"},
50
})
51
52
require.Nil(t, err)
53
expected := `| key | value |
54
| a | b |
55
| c | |
56
| d | e |
57
`
58
require.Equal(t, expected, table)
59
}
60
61
func Test_ShouldFilter_Tracker(t *testing.T) {
62
jiraIntegration := &Integration{
63
options: &Options{AllowList: &filters.Filter{
64
Severities: severity.Severities{severity.Critical},
65
}},
66
}
67
68
require.False(t, jiraIntegration.ShouldFilter(&output.ResultEvent{Info: model.Info{
69
SeverityHolder: severity.Holder{Severity: severity.Info},
70
}}))
71
require.True(t, jiraIntegration.ShouldFilter(&output.ResultEvent{Info: model.Info{
72
SeverityHolder: severity.Holder{Severity: severity.Critical},
73
}}))
74
75
t.Run("deny-list", func(t *testing.T) {
76
jiraIntegration := &Integration{
77
options: &Options{DenyList: &filters.Filter{
78
Severities: severity.Severities{severity.Critical},
79
}},
80
}
81
82
require.True(t, jiraIntegration.ShouldFilter(&output.ResultEvent{Info: model.Info{
83
SeverityHolder: severity.Holder{Severity: severity.Info},
84
}}))
85
require.False(t, jiraIntegration.ShouldFilter(&output.ResultEvent{Info: model.Info{
86
SeverityHolder: severity.Holder{Severity: severity.Critical},
87
}}))
88
})
89
}
90
91
func TestTemplateEvaluation(t *testing.T) {
92
event := &output.ResultEvent{
93
Host: "example.com",
94
Info: model.Info{
95
Name: "Test vulnerability",
96
SeverityHolder: severity.Holder{Severity: severity.Critical},
97
Classification: &model.Classification{
98
CVSSScore: 9.8,
99
CVEID: stringslice.StringSlice{Value: []string{"CVE-2023-1234"}},
100
CWEID: stringslice.StringSlice{Value: []string{"CWE-79"}},
101
CVSSMetrics: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
102
},
103
},
104
}
105
106
integration := &Integration{}
107
108
t.Run("conditional template", func(t *testing.T) {
109
templateStr := `{{if eq .Severity "critical"}}11187{{else if eq .Severity "high"}}11186{{else if eq .Severity "medium"}}11185{{else}}11184{{end}}`
110
result, err := integration.evaluateCustomFieldValue(templateStr, buildTemplateContext(event), event)
111
require.NoError(t, err)
112
require.Equal(t, "11187", result)
113
})
114
115
t.Run("freeform description template", func(t *testing.T) {
116
templateStr := `Vulnerability detected by Nuclei. Name: {{.Name}}, Severity: {{.Severity}}, Host: {{.Host}}`
117
result, err := integration.evaluateCustomFieldValue(templateStr, buildTemplateContext(event), event)
118
require.NoError(t, err)
119
expected := "Vulnerability detected by Nuclei. Name: Test vulnerability, Severity: critical, Host: example.com"
120
require.Equal(t, expected, result)
121
})
122
123
t.Run("legacy variable syntax", func(t *testing.T) {
124
result, err := integration.evaluateCustomFieldValue("$Severity", buildTemplateContext(event), event)
125
require.NoError(t, err)
126
require.Equal(t, "critical", result)
127
128
result, err = integration.evaluateCustomFieldValue("$Host", buildTemplateContext(event), event)
129
require.NoError(t, err)
130
require.Equal(t, "example.com", result)
131
})
132
133
t.Run("complex template with conditionals", func(t *testing.T) {
134
templateStr := `{{.Name}} on {{.Host}}
135
{{if .CVSSScore}}CVSS: {{.CVSSScore}}{{end}}
136
{{if eq .Severity "critical"}}⚠️ CRITICAL{{else}}Standard{{end}}`
137
result, err := integration.evaluateCustomFieldValue(templateStr, buildTemplateContext(event), event)
138
require.NoError(t, err)
139
require.Contains(t, result, "Test vulnerability on example.com")
140
require.Contains(t, result, "CVSS: 9.80")
141
require.Contains(t, result, "⚠️ CRITICAL")
142
})
143
144
t.Run("no template syntax", func(t *testing.T) {
145
result, err := integration.evaluateCustomFieldValue("plain text", buildTemplateContext(event), event)
146
require.NoError(t, err)
147
require.Equal(t, "plain text", result)
148
})
149
150
t.Run("template functions", func(t *testing.T) {
151
// Test case conversion functions
152
result, err := integration.evaluateCustomFieldValue("{{.Severity | upper}}", buildTemplateContext(event), event)
153
require.NoError(t, err)
154
require.Equal(t, "CRITICAL", result)
155
156
result, err = integration.evaluateCustomFieldValue("{{.Name | lower}}", buildTemplateContext(event), event)
157
require.NoError(t, err)
158
require.Equal(t, "test vulnerability", result)
159
160
result, err = integration.evaluateCustomFieldValue("{{.Name | title}}", buildTemplateContext(event), event)
161
require.NoError(t, err)
162
require.Equal(t, "Test Vulnerability", result)
163
164
// Test string check functions
165
result, err = integration.evaluateCustomFieldValue(`{{if contains .Name "Test"}}has-test{{else}}no-test{{end}}`, buildTemplateContext(event), event)
166
require.NoError(t, err)
167
require.Equal(t, "has-test", result)
168
169
result, err = integration.evaluateCustomFieldValue(`{{if hasPrefix .Host "example"}}starts-with-example{{else}}other{{end}}`, buildTemplateContext(event), event)
170
require.NoError(t, err)
171
require.Equal(t, "starts-with-example", result)
172
173
result, err = integration.evaluateCustomFieldValue(`{{if hasSuffix .Host ".com"}}ends-with-com{{else}}other{{end}}`, buildTemplateContext(event), event)
174
require.NoError(t, err)
175
require.Equal(t, "ends-with-com", result)
176
177
// Test string manipulation functions
178
result, err = integration.evaluateCustomFieldValue(`{{replace .Name " " "-"}}`, buildTemplateContext(event), event)
179
require.NoError(t, err)
180
require.Equal(t, "Test-vulnerability", result)
181
182
result, err = integration.evaluateCustomFieldValue(`{{trimSpace " test "}}`, buildTemplateContext(event), event)
183
require.NoError(t, err)
184
require.Equal(t, "test", result)
185
186
result, err = integration.evaluateCustomFieldValue(`{{trim "...test..." "."}}`, buildTemplateContext(event), event)
187
require.NoError(t, err)
188
require.Equal(t, "test", result)
189
190
// Test split and join functions
191
result, err = integration.evaluateCustomFieldValue(`{{join (split .Name " ") "-"}}`, buildTemplateContext(event), event)
192
require.NoError(t, err)
193
require.Equal(t, "Test-vulnerability", result)
194
})
195
196
t.Run("complex template with functions", func(t *testing.T) {
197
templateStr := `{{.Name | upper}} on {{.Host}}
198
{{if contains .Name "SQL"}}SQL-INJECTION{{else if contains .Name "XSS"}}XSS-ATTACK{{else}}OTHER{{end}}
199
Priority: {{if eq .Severity "critical"}}{{.Severity | upper}}{{else}}{{.Severity}}{{end}}`
200
result, err := integration.evaluateCustomFieldValue(templateStr, buildTemplateContext(event), event)
201
require.NoError(t, err)
202
require.Contains(t, result, "TEST VULNERABILITY on example.com", result)
203
require.Contains(t, result, "OTHER")
204
require.Contains(t, result, "CRITICAL")
205
})
206
}
207
208
// Live test to verify SearchV2JQL hits /rest/api/3/search/jql when creds are provided via env
209
func TestJiraLive_SearchV2UsesJqlEndpoint(t *testing.T) {
210
jiraURL := os.Getenv("JIRA_URL")
211
jiraEmail := os.Getenv("JIRA_EMAIL")
212
jiraAccountID := os.Getenv("JIRA_ACCOUNT_ID")
213
jiraToken := os.Getenv("JIRA_TOKEN")
214
jiraPAT := os.Getenv("JIRA_PAT")
215
jiraProjectName := os.Getenv("JIRA_PROJECT_NAME")
216
jiraProjectID := os.Getenv("JIRA_PROJECT_ID")
217
jiraStatusNot := os.Getenv("JIRA_STATUS_NOT")
218
jiraCloud := os.Getenv("JIRA_CLOUD")
219
220
if jiraURL == "" || (jiraPAT == "" && jiraToken == "") || (jiraEmail == "" && jiraAccountID == "") || (jiraProjectName == "" && jiraProjectID == "") {
221
t.Skip("live Jira test skipped: missing JIRA_* env vars")
222
}
223
224
statusNot := jiraStatusNot
225
if statusNot == "" {
226
statusNot = "Done"
227
}
228
229
isCloud := !strings.EqualFold(jiraCloud, "false") && jiraCloud != "0"
230
231
rec := &recordingTransport{}
232
rc := retryablehttp.NewClient(retryablehttp.DefaultOptionsSingle)
233
rc.HTTPClient.Transport = rec
234
235
opts := &Options{
236
Cloud: isCloud,
237
URL: jiraURL,
238
Email: jiraEmail,
239
AccountID: jiraAccountID,
240
Token: jiraToken,
241
PersonalAccessToken: jiraPAT,
242
ProjectName: jiraProjectName,
243
ProjectID: jiraProjectID,
244
IssueType: "Task",
245
StatusNot: statusNot,
246
HttpClient: rc,
247
}
248
249
integration, err := New(opts)
250
require.NoError(t, err)
251
252
event := &output.ResultEvent{
253
Host: "example.com",
254
Info: model.Info{
255
Name: "Nuclei Live Verify",
256
SeverityHolder: severity.Holder{Severity: severity.Low},
257
},
258
}
259
260
_, _ = integration.FindExistingIssue(event, true)
261
262
var hitSearchV2 bool
263
for _, p := range rec.paths {
264
if strings.HasSuffix(p, "/rest/api/3/search/jql") {
265
hitSearchV2 = true
266
break
267
}
268
}
269
require.True(t, hitSearchV2, "expected client to call /rest/api/3/search/jql, got paths: %v", rec.paths)
270
}
271
272