1
1
from .base import Plugin
2
2
import requests
3
3
import base64
4
+ from socketsecurity .core .classes import Diff
5
+ from socketsecurity .config import CliConfig
6
+ from socketsecurity .core import log
7
+
4
8
5
9
class JiraPlugin (Plugin ):
6
- def send (self , message , level ):
10
+ def send (self , diff : Diff , config : CliConfig ):
7
11
if not self .config .get ("enabled" , False ):
8
12
return
9
- if level not in self .config .get ("levels" , ["block" , "warn" ]):
10
- return
13
+ log .debug ("Jira Plugin Enabled" )
14
+ alert_levels = self .config .get ("levels" , ["block" , "warn" ])
15
+ log .debug (f"Alert levels: { alert_levels } " )
16
+ # has_blocking = any(getattr(a, "blocking", False) for a in diff.new_alerts)
17
+ # if "block" not in alert_levels and has_blocking:
18
+ # return
19
+ # if "warn" not in alert_levels and not has_blocking:
20
+ # return
21
+ parts = ["Security Issues found in Socket Security results" ]
22
+ pr = getattr (config , "pr_number" , "" )
23
+ sha = getattr (config , "commit_sha" , "" )[:8 ] if getattr (config , "commit_sha" , "" ) else ""
24
+ scan_link = getattr (diff , "diff_url" , "" )
25
+
26
+ if pr and pr != "0" :
27
+ parts .append (f"for PR { pr } " )
28
+ if sha :
29
+ parts .append (f"- { sha } " )
30
+ title = " " .join (parts )
11
31
32
+ description_adf = {
33
+ "type" : "doc" ,
34
+ "version" : 1 ,
35
+ "content" : [
36
+ {
37
+ "type" : "paragraph" ,
38
+ "content" : [
39
+ {"type" : "text" , "text" : "Security issues were found in this scan:" },
40
+ {"type" : "text" , "text" : "\n " },
41
+ {
42
+ "type" : "text" ,
43
+ "text" : "View Socket Security scan results" ,
44
+ "marks" : [{"type" : "link" , "attrs" : {"href" : scan_link }}]
45
+ }
46
+ ]
47
+ },
48
+ self .create_adf_table_from_diff (diff )
49
+ ]
50
+ }
51
+ # log.debug("ADF Description Payload:\n" + json.dumps(description_adf, indent=2))
52
+ log .debug ("Sending Jira Issue" )
53
+ # 🛠️ Build and send the Jira issue
12
54
url = self .config ["url" ]
13
55
project = self .config ["project" ]
14
- auth = base64 .b64encode (f"{ self .config ['email' ]} :{ self .config ['api_token' ]} " .encode ()).decode ()
56
+ auth = base64 .b64encode (
57
+ f"{ self .config ['email' ]} :{ self .config ['api_token' ]} " .encode ()
58
+ ).decode ()
15
59
16
60
payload = {
17
61
"fields" : {
18
62
"project" : {"key" : project },
19
- "summary" : message . get ( " title" , "No title" ) ,
20
- "description" : message . get ( "description" , "" ) ,
63
+ "summary" : title ,
64
+ "description" : description_adf ,
21
65
"issuetype" : {"name" : "Task" }
22
66
}
23
67
}
@@ -26,5 +70,89 @@ def send(self, message, level):
26
70
"Authorization" : f"Basic { auth } " ,
27
71
"Content-Type" : "application/json"
28
72
}
73
+ jira_url = f"{ url } /rest/api/3/issue"
74
+ log .debug (f"Jira URL: { jira_url } " )
75
+ response = requests .post (jira_url , json = payload , headers = headers )
76
+ if response .status_code >= 300 :
77
+ log .error (f"Jira error { response .status_code } : { response .text } " )
78
+ else :
79
+ log .info (f"Jira ticket created: { response .json ().get ('key' )} " )
80
+
81
+ @staticmethod
82
+ def flatten_adf_to_text (adf ):
83
+ def extract_text (node ):
84
+ if isinstance (node , dict ):
85
+ if node .get ("type" ) == "text" :
86
+ return node .get ("text" , "" )
87
+ return "" .join (extract_text (child ) for child in node .get ("content" , []))
88
+ elif isinstance (node , list ):
89
+ return "" .join (extract_text (child ) for child in node )
90
+ return ""
29
91
30
- requests .post (f"{ url } /rest/api/3/issue" , json = payload , headers = headers )
92
+ return extract_text (adf )
93
+
94
+ @staticmethod
95
+ def create_adf_table_from_diff (diff ):
96
+ from socketsecurity .core .messages import Messages
97
+
98
+ def make_cell (text ):
99
+ return {
100
+ "type" : "tableCell" ,
101
+ "content" : [
102
+ {
103
+ "type" : "paragraph" ,
104
+ "content" : [{"type" : "text" , "text" : text }]
105
+ }
106
+ ]
107
+ }
108
+
109
+ def make_link_cell (text , url ):
110
+ return {
111
+ "type" : "tableCell" ,
112
+ "content" : [
113
+ {
114
+ "type" : "paragraph" ,
115
+ "content" : [{
116
+ "type" : "text" ,
117
+ "text" : text ,
118
+ "marks" : [{"type" : "link" , "attrs" : {"href" : url }}]
119
+ }]
120
+ }
121
+ ]
122
+ }
123
+
124
+ # Header row (must use tableCell not tableHeader!)
125
+ header_row = {
126
+ "type" : "tableRow" ,
127
+ "content" : [
128
+ make_cell ("Alert" ),
129
+ make_cell ("Package" ),
130
+ make_cell ("Introduced by" ),
131
+ make_cell ("Manifest File" ),
132
+ make_cell ("CI" )
133
+ ]
134
+ }
135
+
136
+ rows = [header_row ]
137
+
138
+ for alert in diff .new_alerts :
139
+ manifest_str , source_str = Messages .create_sources (alert , "plain" )
140
+
141
+ row = {
142
+ "type" : "tableRow" ,
143
+ "content" : [
144
+ make_cell (alert .title ),
145
+ make_link_cell (alert .purl , alert .url ) if alert .url else make_cell (alert .purl ),
146
+ make_cell (source_str ),
147
+ make_cell (manifest_str ),
148
+ make_cell ("🚫" if alert .error else "⚠️" )
149
+ ]
150
+ }
151
+
152
+ rows .append (row )
153
+
154
+ # Final return is a block array
155
+ return {
156
+ "type" : "table" ,
157
+ "content" : rows
158
+ }
0 commit comments