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