1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.owasp.dependencycheck.data.nodeaudit;
19
20 import java.io.IOException;
21 import java.net.MalformedURLException;
22 import java.net.URISyntaxException;
23 import java.net.URL;
24 import java.security.SecureRandom;
25 import java.util.ArrayList;
26 import java.util.List;
27 import javax.annotation.concurrent.ThreadSafe;
28
29 import org.apache.hc.client5.http.HttpResponseException;
30 import org.apache.hc.core5.http.ContentType;
31 import org.apache.hc.core5.http.Header;
32 import org.apache.hc.core5.http.HttpHeaders;
33 import org.apache.hc.core5.http.message.BasicHeader;
34 import org.json.JSONObject;
35 import org.owasp.dependencycheck.utils.DownloadFailedException;
36 import org.owasp.dependencycheck.utils.Downloader;
37 import org.owasp.dependencycheck.utils.ResourceNotFoundException;
38 import org.owasp.dependencycheck.utils.Settings;
39 import org.owasp.dependencycheck.utils.TooManyRequestsException;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42
43 import jakarta.json.JsonObject;
44 import org.apache.commons.jcs3.access.exception.CacheException;
45
46 import static org.owasp.dependencycheck.analyzer.NodeAuditAnalyzer.DEFAULT_URL;
47
48 import org.owasp.dependencycheck.analyzer.exception.SearchException;
49 import org.owasp.dependencycheck.analyzer.exception.UnexpectedAnalysisException;
50 import org.owasp.dependencycheck.data.cache.DataCache;
51 import org.owasp.dependencycheck.data.cache.DataCacheFactory;
52 import org.owasp.dependencycheck.utils.Checksum;
53
54
55
56
57
58
59 @ThreadSafe
60 public class NodeAuditSearch {
61
62
63
64
65 private final URL nodeAuditUrl;
66
67
68
69
70 private final boolean useProxy;
71
72
73
74 private final Settings settings;
75
76
77
78 private static final Logger LOGGER = LoggerFactory.getLogger(NodeAuditSearch.class);
79
80
81
82 private DataCache<List<Advisory>> cache;
83
84
85
86
87
88
89
90
91 public NodeAuditSearch(Settings settings) throws MalformedURLException {
92 final String searchUrl = settings.getString(Settings.KEYS.ANALYZER_NODE_AUDIT_URL, DEFAULT_URL);
93 LOGGER.debug("Node Audit Search URL: {}", searchUrl);
94 this.nodeAuditUrl = new URL(searchUrl);
95 this.settings = settings;
96 if (null != settings.getString(Settings.KEYS.PROXY_SERVER)) {
97 useProxy = true;
98 LOGGER.debug("Using proxy");
99 } else {
100 useProxy = false;
101 LOGGER.debug("Not using proxy");
102 }
103 if (settings.getBoolean(Settings.KEYS.ANALYZER_NODE_AUDIT_USE_CACHE, true)) {
104 try {
105 final DataCacheFactory factory = new DataCacheFactory(settings);
106 cache = factory.getNodeAuditCache();
107 } catch (CacheException ex) {
108 settings.setBoolean(Settings.KEYS.ANALYZER_NODE_AUDIT_USE_CACHE, false);
109 LOGGER.debug("Error creating cache, disabling caching", ex);
110 }
111 }
112 }
113
114
115
116
117
118
119
120
121
122
123
124 public List<Advisory> submitPackage(JsonObject packageJson) throws SearchException, IOException {
125 String key = null;
126 if (cache != null) {
127 key = Checksum.getSHA256Checksum(packageJson.toString());
128 final List<Advisory> cached = cache.get(key);
129 if (cached != null) {
130 LOGGER.debug("cache hit for node audit: " + key);
131 return cached;
132 }
133 }
134 return submitPackage(packageJson, key, 0);
135 }
136
137
138
139
140
141
142
143
144
145
146
147
148
149 private List<Advisory> submitPackage(JsonObject packageJson, String key, int count) throws SearchException, IOException {
150 if (LOGGER.isTraceEnabled()) {
151 LOGGER.trace("----------------------------------------");
152 LOGGER.trace("Node Audit Payload:");
153 LOGGER.trace(packageJson.toString());
154 LOGGER.trace("----------------------------------------");
155 LOGGER.trace("----------------------------------------");
156 }
157 final List<Header> additionalHeaders = new ArrayList<>();
158 additionalHeaders.add(new BasicHeader(HttpHeaders.USER_AGENT, "npm/6.1.0 node/v10.5.0 linux x64"));
159 additionalHeaders.add(new BasicHeader("npm-in-ci", "false"));
160 additionalHeaders.add(new BasicHeader("npm-scope", ""));
161 additionalHeaders.add(new BasicHeader("npm-session", generateRandomSession()));
162
163 try {
164 final String response = Downloader.getInstance().postBasedFetchContent(nodeAuditUrl.toURI(),
165 packageJson.toString(), ContentType.APPLICATION_JSON, additionalHeaders);
166 final JSONObject jsonResponse = new JSONObject(response);
167 final NpmAuditParser parser = new NpmAuditParser();
168 final List<Advisory> advisories = parser.parse(jsonResponse);
169 if (cache != null) {
170 cache.put(key, advisories);
171 }
172 return advisories;
173 } catch (RuntimeException | URISyntaxException | TooManyRequestsException | ResourceNotFoundException ex) {
174 LOGGER.debug("Error connecting to Node Audit API. Error: {}",
175 ex.getMessage());
176 throw new SearchException("Could not connect to Node Audit API: " + ex.getMessage(), ex);
177 } catch (DownloadFailedException e) {
178 if (e.getCause() instanceof HttpResponseException) {
179 final HttpResponseException hre = (HttpResponseException) e.getCause();
180 switch (hre.getStatusCode()) {
181 case 503:
182 LOGGER.debug("Node Audit API returned `{} {}` - retrying request.",
183 hre.getStatusCode(), hre.getReasonPhrase());
184 if (count < 5) {
185 final int next = count + 1;
186 try {
187 Thread.sleep(1500L * next);
188 } catch (InterruptedException ex) {
189 Thread.currentThread().interrupt();
190 throw new UnexpectedAnalysisException(ex);
191 }
192 return submitPackage(packageJson, key, next);
193 }
194 throw new SearchException("Could not perform Node Audit analysis - service returned a 503.", e);
195 case 400:
196 LOGGER.debug("Invalid payload submitted to Node Audit API. Received response code: {} {}",
197 hre.getStatusCode(), hre.getReasonPhrase());
198 throw new SearchException("Could not perform Node Audit analysis. Invalid payload submitted to Node Audit API.", e);
199 default:
200 LOGGER.debug("Could not connect to Node Audit API. Received response code: {} {}",
201 hre.getStatusCode(), hre.getReasonPhrase());
202 throw new IOException("Could not connect to Node Audit API", e);
203 }
204 } else {
205 LOGGER.debug("Could not connect to Node Audit API. Received generic DownloadException", e);
206 throw new IOException("Could not connect to Node Audit API", e);
207 }
208 }
209 }
210
211
212
213
214
215
216 private String generateRandomSession() {
217 final int length = 16;
218 final SecureRandom r = new SecureRandom();
219 final StringBuilder sb = new StringBuilder();
220 while (sb.length() < length) {
221 sb.append(Integer.toHexString(r.nextInt()));
222 }
223 return sb.substring(0, length);
224 }
225 }