1+ package edu .hm .hafner .analysis .parser ;
2+
3+ import org .json .JSONArray ;
4+ import org .json .JSONObject ;
5+
6+ import edu .hm .hafner .analysis .Issue ;
7+ import edu .hm .hafner .analysis .IssueBuilder ;
8+ import edu .hm .hafner .analysis .Report ;
9+ import edu .umd .cs .findbugs .annotations .CheckForNull ;
10+
11+ import java .io .Serial ;
12+ import java .util .ArrayList ;
13+ import java .util .List ;
14+
15+ /**
16+ * A parser for AWS CodeGuru Security JSON output.
17+ *
18+ * @author Akash Manna
19+ * @see <a href="https://docs.aws.amazon.com/cli/latest/reference/codeguru-security/get-findings.html">AWS CodeGuru
20+ * Security get-findings</a>
21+ */
22+ public class CodeGuruSecurityParser extends JsonIssueParser {
23+ @ Serial
24+ private static final long serialVersionUID = 1187547319271125446L ;
25+
26+ private static final String FINDINGS = "findings" ;
27+ private static final String TITLE = "title" ;
28+ private static final String DESCRIPTION = "description" ;
29+ private static final String SEVERITY = "severity" ;
30+ private static final String TYPE = "type" ;
31+ private static final String RULE_ID = "ruleId" ;
32+ private static final String DETECTOR_ID = "detectorId" ;
33+ private static final String DETECTOR_NAME = "detectorName" ;
34+ private static final String VULNERABILITY = "vulnerability" ;
35+ private static final String FILE_PATH = "filePath" ;
36+ private static final String PATH = "path" ;
37+ private static final String NAME = "name" ;
38+ private static final String START_LINE = "startLine" ;
39+ private static final String END_LINE = "endLine" ;
40+ private static final String RECOMMENDATION = "recommendation" ;
41+ private static final String TEXT = "text" ;
42+ private static final String URL = "url" ;
43+ private static final String REFERENCE_URLS = "referenceUrls" ;
44+ private static final String SUGGESTED_FIXES = "suggestedFixes" ;
45+ private static final String FIX_TITLE = "title" ;
46+ private static final String FIX_DESCRIPTION = "description" ;
47+ private static final String CODE_SNIPPET = "codeSnippet" ;
48+ private static final String CONTENT = "content" ;
49+ private static final String NUMBER = "number" ;
50+
51+ @ Override
52+ protected void parseJsonObject (final Report report , final JSONObject jsonReport , final IssueBuilder issueBuilder ) {
53+ var findings = jsonReport .optJSONArray (FINDINGS );
54+ if (findings == null ) {
55+ return ;
56+ }
57+
58+ for (int i = 0 ; i < findings .length (); i ++) {
59+ var finding = findings .optJSONObject (i );
60+ if (finding != null ) {
61+ report .add (convertToIssue (finding , issueBuilder ));
62+ }
63+ }
64+ }
65+
66+ private Issue convertToIssue (final JSONObject finding , final IssueBuilder issueBuilder ) {
67+ var vulnerability = finding .optJSONObject (VULNERABILITY );
68+
69+ issueBuilder
70+ .setType (firstNonBlank (finding , RULE_ID , DETECTOR_ID , DETECTOR_NAME , TYPE ))
71+ .setMessage (finding .optString (TITLE , "" ))
72+ .guessSeverity (finding .optString (SEVERITY , "Info" ))
73+ .setDescription (buildDescription (finding , vulnerability ));
74+
75+ if (vulnerability != null ) {
76+ var filePath = vulnerability .optJSONObject (FILE_PATH );
77+ var fileName = firstNonBlank (filePath , PATH , NAME );
78+ issueBuilder .setFileName (fileName );
79+ if (!fileName .isEmpty ()) {
80+ issueBuilder .setLineStart (filePath .optInt (START_LINE , 0 ))
81+ .setLineEnd (filePath .optInt (END_LINE , 0 ));
82+ }
83+ }
84+
85+ return issueBuilder .buildAndClean ();
86+ }
87+
88+ private String buildDescription (final JSONObject finding , @ CheckForNull final JSONObject vulnerability ) {
89+ var sections = new ArrayList <String >();
90+
91+ appendIfNotBlank (sections , finding .optString (DESCRIPTION , "" ));
92+
93+ var recommendation = finding .optJSONObject (RECOMMENDATION );
94+ if (recommendation != null ) {
95+ appendIfNotBlank (sections , recommendation .optString (TEXT , "" ));
96+ appendIfNotBlank (sections , recommendation .optString (URL , "" ));
97+ }
98+
99+ appendReferenceUrls (sections , vulnerability );
100+ appendSuggestedFixes (sections , finding .optJSONArray (SUGGESTED_FIXES ));
101+ appendCodeSnippets (sections , vulnerability == null ? null : vulnerability .optJSONArray (CODE_SNIPPET ));
102+
103+ return String .join ("\n \n " , sections );
104+ }
105+
106+ private void appendReferenceUrls (final List <String > sections , @ CheckForNull final JSONObject vulnerability ) {
107+ if (vulnerability == null ) {
108+ return ;
109+ }
110+
111+ var referenceUrls = vulnerability .optJSONArray (REFERENCE_URLS );
112+ if (referenceUrls == null || referenceUrls .isEmpty ()) {
113+ return ;
114+ }
115+
116+ var urls = new ArrayList <String >();
117+ for (int i = 0 ; i < referenceUrls .length (); i ++) {
118+ var url = referenceUrls .optString (i , "" ).trim ();
119+ if (!url .isBlank ()) {
120+ urls .add (url );
121+ }
122+ }
123+
124+ if (!urls .isEmpty ()) {
125+ sections .add ("References: " + String .join (", " , urls ));
126+ }
127+ }
128+
129+ private void appendSuggestedFixes (final List <String > sections , @ CheckForNull final JSONArray suggestedFixes ) {
130+ if (suggestedFixes == null || suggestedFixes .isEmpty ()) {
131+ return ;
132+ }
133+
134+ var fixes = new ArrayList <String >();
135+ for (int i = 0 ; i < suggestedFixes .length (); i ++) {
136+ var fix = formatSuggestedFix (suggestedFixes .optJSONObject (i ));
137+ if (fix != null ) {
138+ fixes .add (fix );
139+ }
140+ }
141+
142+ if (!fixes .isEmpty ()) {
143+ sections .add ("Suggested fixes: " + String .join (" | " , fixes ));
144+ }
145+ }
146+
147+ @ CheckForNull
148+ private String formatSuggestedFix (@ CheckForNull final JSONObject suggestedFix ) {
149+ if (suggestedFix == null ) {
150+ return null ;
151+ }
152+
153+ var title = suggestedFix .optString (FIX_TITLE , "" ).trim ();
154+ var description = suggestedFix .optString (FIX_DESCRIPTION , "" ).trim ();
155+ if (title .isBlank ()) {
156+ return description .isBlank () ? null : description ;
157+ }
158+ if (description .isBlank ()) {
159+ return title ;
160+ }
161+
162+ return title + ": " + description ;
163+ }
164+
165+ private void appendCodeSnippets (final List <String > sections , @ CheckForNull final JSONArray codeSnippet ) {
166+ if (codeSnippet == null || codeSnippet .isEmpty ()) {
167+ return ;
168+ }
169+
170+ var lines = new ArrayList <String >();
171+ for (int i = 0 ; i < codeSnippet .length (); i ++) {
172+ var codeLine = codeSnippet .optJSONObject (i );
173+ if (codeLine == null ) {
174+ continue ;
175+ }
176+
177+ var number = codeLine .optInt (NUMBER , 0 );
178+ var content = codeLine .optString (CONTENT , "" ).trim ();
179+ if (!content .isBlank ()) {
180+ lines .add (number > 0 ? number + ": " + content : content );
181+ }
182+ }
183+
184+ if (!lines .isEmpty ()) {
185+ sections .add ("Code snippet:\n " + String .join ("\n " , lines ));
186+ }
187+ }
188+
189+ private void appendIfNotBlank (final List <String > sections , final String value ) {
190+ var trimmed = value .trim ();
191+ if (!trimmed .isBlank ()) {
192+ sections .add (trimmed );
193+ }
194+ }
195+ }
0 commit comments