11from .base import Plugin
22import requests
33import base64
4+ from socketsecurity .core .classes import Diff
5+ from socketsecurity .config import CliConfig
6+ from socketsecurity .core import log
7+
48
59class JiraPlugin (Plugin ):
6- def send (self , message , level ):
10+ def send (self , diff : Diff , config : CliConfig ):
711 if not self .config .get ("enabled" , False ):
812 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 )
1131
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
1254 url = self .config ["url" ]
1355 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 ()
1559
1660 payload = {
1761 "fields" : {
1862 "project" : {"key" : project },
19- "summary" : message . get ( " title" , "No title" ) ,
20- "description" : message . get ( "description" , "" ) ,
63+ "summary" : title ,
64+ "description" : description_adf ,
2165 "issuetype" : {"name" : "Task" }
2266 }
2367 }
@@ -26,5 +70,89 @@ def send(self, message, level):
2670 "Authorization" : f"Basic { auth } " ,
2771 "Content-Type" : "application/json"
2872 }
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 ""
2991
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