1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.owasp.dependencycheck.data.update;
19
20 import org.owasp.dependencycheck.Engine;
21 import org.owasp.dependencycheck.data.update.exception.UpdateException;
22 import org.owasp.dependencycheck.exception.WriteLockException;
23 import org.owasp.dependencycheck.utils.Downloader;
24 import org.owasp.dependencycheck.utils.ResourceNotFoundException;
25 import org.owasp.dependencycheck.utils.Settings;
26 import org.owasp.dependencycheck.utils.TooManyRequestsException;
27 import org.owasp.dependencycheck.utils.WriteLock;
28 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory;
30
31 import java.io.File;
32 import java.io.IOException;
33 import java.net.MalformedURLException;
34 import java.net.URL;
35 import java.nio.file.Files;
36 import org.owasp.dependencycheck.data.nvdcve.DatabaseProperties;
37
38 public class HostedSuppressionsDataSource implements CachedWebDataSource {
39
40
41
42
43 private static final Logger LOGGER = LoggerFactory.getLogger(HostedSuppressionsDataSource.class);
44
45
46
47
48 private Settings settings;
49
50
51
52 private DatabaseProperties dbProperties = null;
53
54
55
56 public static final String DEFAULT_SUPPRESSIONS_URL = "https://jeremylong.github.io/DependencyCheck/suppressions/publishedSuppressions.xml";
57
58
59
60
61
62
63
64
65
66 @Override
67 public boolean update(Engine engine) throws UpdateException {
68 this.settings = engine.getSettings();
69 if (engine.getMode() != Engine.Mode.EVIDENCE_COLLECTION) {
70
71 this.dbProperties = engine.getDatabase().getDatabaseProperties();
72 }
73 final String configuredUrl = settings.getString(Settings.KEYS.HOSTED_SUPPRESSIONS_URL, DEFAULT_SUPPRESSIONS_URL);
74 final boolean autoupdate = settings.getBoolean(Settings.KEYS.AUTO_UPDATE, true);
75 final boolean forceupdate = settings.getBoolean(Settings.KEYS.HOSTED_SUPPRESSIONS_FORCEUPDATE, false);
76 final boolean cpeSuppressionEnabled = settings.getBoolean(Settings.KEYS.ANALYZER_CPE_SUPPRESSION_ENABLED, true);
77 final boolean vulnSuppressionEnabled = settings.getBoolean(Settings.KEYS.ANALYZER_VULNERABILITY_SUPPRESSION_ENABLED, true);
78 boolean enabled = settings.getBoolean(Settings.KEYS.HOSTED_SUPPRESSIONS_ENABLED, true);
79 enabled = enabled && (cpeSuppressionEnabled || vulnSuppressionEnabled);
80 try {
81 final URL url = new URL(configuredUrl);
82 final File filepath = new File(url.getPath());
83 final File repoFile = new File(settings.getDataDirectory(), filepath.getName());
84 final boolean proceed = enabled && (forceupdate || (autoupdate && shouldUpdate(repoFile)));
85 if (proceed) {
86 LOGGER.debug("Begin Hosted Suppressions file update");
87 fetchHostedSuppressions(settings, url, repoFile);
88 if (dbProperties != null) {
89 dbProperties.save(DatabaseProperties.HOSTED_SUPPRESSION_LAST_CHECKED, Long.toString(System.currentTimeMillis() / 1000));
90 }
91 }
92 } catch (UpdateException ex) {
93
94 LOGGER.warn("Failed to update hosted suppressions file, results may contain false positives already resolved by the "
95 + "DependencyCheck project", ex);
96 } catch (MalformedURLException ex) {
97 throw new UpdateException(String.format("Invalid URL for Hosted Suppressions file (%s)", configuredUrl), ex);
98 } catch (IOException ex) {
99 throw new UpdateException("Unable to get the data directory", ex);
100 }
101 return false;
102 }
103
104
105
106
107
108
109
110
111
112
113 protected boolean shouldUpdate(File repo) throws NumberFormatException {
114 boolean proceed = true;
115 if (repo != null && repo.isFile()) {
116 final int validForHours = settings.getInt(Settings.KEYS.HOSTED_SUPPRESSIONS_VALID_FOR_HOURS, 2);
117 long lastUpdatedOn = 0;
118 if (dbProperties != null) {
119 lastUpdatedOn = dbProperties.getPropertyInSeconds(DatabaseProperties.HOSTED_SUPPRESSION_LAST_CHECKED);
120 }
121 if (lastUpdatedOn <= 0) {
122
123 lastUpdatedOn = repo.lastModified();
124 }
125 final long now = System.currentTimeMillis();
126 LOGGER.debug("Last updated: {}", lastUpdatedOn);
127 LOGGER.debug("Now: {}", now);
128 final long msValid = validForHours * 60L * 60L * 1000L;
129 proceed = (now - lastUpdatedOn) > msValid;
130 if (!proceed) {
131 LOGGER.info("Skipping Hosted Suppressions file update since last update was within {} hours.", validForHours);
132 }
133 }
134 return proceed;
135 }
136
137
138
139
140
141
142
143
144
145
146
147 @SuppressWarnings("try")
148 private void fetchHostedSuppressions(Settings settings, URL repoUrl, File repoFile) throws UpdateException {
149 try (WriteLock ignored = new WriteLock(settings, true, repoFile.getName() + ".lock")) {
150 if (LOGGER.isDebugEnabled()) {
151 LOGGER.debug("Hosted Suppressions URL: {}", repoUrl.toExternalForm());
152 }
153 final Downloader downloader = new Downloader(settings);
154 downloader.fetchFile(repoUrl, repoFile);
155 } catch (IOException | TooManyRequestsException | ResourceNotFoundException | WriteLockException ex) {
156 throw new UpdateException("Failed to update the hosted suppressions file", ex);
157 }
158 }
159
160 @Override
161 @SuppressWarnings("try")
162 public boolean purge(Engine engine) {
163 this.settings = engine.getSettings();
164 boolean result = true;
165 try {
166 final URL repoUrl = new URL(settings.getString(Settings.KEYS.HOSTED_SUPPRESSIONS_URL,
167 DEFAULT_SUPPRESSIONS_URL));
168 final String filename = new File(repoUrl.getPath()).getName();
169 final File repo = new File(settings.getDataDirectory(), filename);
170 if (repo.exists()) {
171 try (WriteLock ignored = new WriteLock(settings, true, filename + ".lock")) {
172 result = deleteCachedFile(repo);
173 }
174 }
175 } catch (WriteLockException | IOException ex) {
176 LOGGER.error("Unable to delete the Hosted suppression file - invalid configuration");
177 result = false;
178 }
179 return result;
180 }
181
182 private boolean deleteCachedFile(final File repo) {
183 boolean deleted = true;
184 try {
185 Files.delete(repo.toPath());
186 LOGGER.info("Hosted suppression file removed successfully");
187 } catch (IOException ex) {
188 LOGGER.error("Unable to delete '{}'; please delete the file manually", repo.getAbsolutePath(), ex);
189 deleted = false;
190 }
191 return deleted;
192 }
193 }