View Javadoc
1   /*
2    * This file is part of dependency-check-core.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   *
16   * Copyright (c) 2014 Jeremy Long. All Rights Reserved.
17   */
18  package org.owasp.dependencycheck.data.update;
19  
20  import java.io.IOException;
21  import java.net.MalformedURLException;
22  import java.net.URL;
23  import java.nio.charset.StandardCharsets;
24  import java.util.Arrays;
25  import javax.annotation.concurrent.ThreadSafe;
26  
27  import org.owasp.dependencycheck.Engine;
28  import org.owasp.dependencycheck.data.nvdcve.CveDB;
29  import org.owasp.dependencycheck.data.nvdcve.DatabaseException;
30  import org.owasp.dependencycheck.data.nvdcve.DatabaseProperties;
31  import org.owasp.dependencycheck.data.update.exception.UpdateException;
32  import org.owasp.dependencycheck.utils.DateUtil;
33  import org.owasp.dependencycheck.utils.DependencyVersion;
34  import org.owasp.dependencycheck.utils.Downloader;
35  import org.owasp.dependencycheck.utils.ResourceNotFoundException;
36  import org.owasp.dependencycheck.utils.Settings;
37  import org.owasp.dependencycheck.utils.TooManyRequestsException;
38  import org.slf4j.Logger;
39  import org.slf4j.LoggerFactory;
40  
41  /**
42   * Checks the gh-pages dependency-check site to determine the current released
43   * version number. If the released version number is greater than the running
44   * version number a warning is printed recommending that an upgrade be
45   * performed.
46   *
47   * @author Jeremy Long
48   */
49  @ThreadSafe
50  public class EngineVersionCheck implements CachedWebDataSource {
51  
52      /**
53       * Static logger.
54       */
55      private static final Logger LOGGER = LoggerFactory.getLogger(EngineVersionCheck.class);
56      /**
57       * The property key indicating when the last version check occurred.
58       */
59      public static final String ENGINE_VERSION_CHECKED_ON = "VersionCheckOn";
60      /**
61       * The property key indicating when the last version check occurred.
62       */
63      public static final String CURRENT_ENGINE_RELEASE = "CurrentEngineRelease";
64      /**
65       * The version retrieved from the database properties or web to check
66       * against.
67       */
68      private String updateToVersion;
69      /**
70       * The configured settings.
71       */
72      private Settings settings;
73  
74      /**
75       * Constructs a new engine version check utility for testing.
76       *
77       * @param settings the configured settings
78       */
79      protected EngineVersionCheck(Settings settings) {
80          this.settings = settings;
81      }
82  
83      /**
84       * Constructs a new engine version check utility.
85       */
86      public EngineVersionCheck() {
87      }
88  
89      /**
90       * Getter for updateToVersion - only used for testing. Represents the
91       * version retrieved from the database.
92       *
93       * @return the version to test
94       */
95      protected String getUpdateToVersion() {
96          return updateToVersion;
97      }
98  
99      /**
100      * Setter for updateToVersion - only used for testing. Represents the
101      * version retrieved from the database.
102      *
103      * @param version the version to test
104      */
105     protected void setUpdateToVersion(String version) {
106         updateToVersion = version;
107     }
108 
109     /**
110      * Downloads the current released version number and compares it to the
111      * running engine's version number. If the released version number is newer
112      * a warning is printed recommending an upgrade.
113      *
114      * @return returns false as no updates are made to the database that would
115      * require compaction
116      * @throws UpdateException thrown if the local database properties could not
117      * be updated
118      */
119     @Override
120     public boolean update(Engine engine) throws UpdateException {
121         this.settings = engine.getSettings();
122         try {
123             final CveDB db = engine.getDatabase();
124             final boolean autoupdate = settings.getBoolean(Settings.KEYS.AUTO_UPDATE, true);
125             final boolean enabled = settings.getBoolean(Settings.KEYS.UPDATE_VERSION_CHECK_ENABLED, true);
126             final String datafeed = settings.getString(Settings.KEYS.NVD_API_DATAFEED_URL);
127             /*
128              * Only update if auto-update is enabled, the engine check is
129              * enabled, and the NVD DataFeed is being used (i.e. the user
130              * is likely on a private network). This check is not really needed
131              * so we are okay skipping it.
132              */
133             if (enabled && autoupdate && datafeed != null) {
134                 LOGGER.debug("Begin Engine Version Check");
135 
136                 final DatabaseProperties properties = db.getDatabaseProperties();
137 
138                 final long lastChecked = DateUtil.getEpochValueInSeconds(properties.getProperty(ENGINE_VERSION_CHECKED_ON, "0"));
139                 final long now = System.currentTimeMillis() / 1000;
140                 updateToVersion = properties.getProperty(CURRENT_ENGINE_RELEASE, "");
141                 final String currentVersion = settings.getString(Settings.KEYS.APPLICATION_VERSION, "0.0.0");
142                 LOGGER.debug("Last checked: {}", lastChecked);
143                 LOGGER.debug("Now: {}", now);
144                 LOGGER.debug("Current version: {}", currentVersion);
145                 final boolean updateNeeded = shouldUpdate(lastChecked, now, properties, currentVersion);
146                 if (updateNeeded) {
147                     LOGGER.warn("A new version of dependency-check is available. Consider updating to version {}.",
148                             updateToVersion);
149                 }
150             }
151         } catch (DatabaseException ex) {
152             LOGGER.debug("Database Exception opening databases to retrieve properties", ex);
153             throw new UpdateException("Error occurred updating database properties.");
154         }
155         return false;
156     }
157 
158     /**
159      * Determines if a new version of the dependency-check engine has been
160      * released.
161      *
162      * @param lastChecked the epoch time of the last version check
163      * @param now the current epoch time
164      * @param properties the database properties object
165      * @param currentVersion the current version of dependency-check
166      * @return <code>true</code> if a newer version of the database has been
167      * released; otherwise <code>false</code>
168      * @throws UpdateException thrown if there is an error connecting to the
169      * github documentation site or accessing the local database.
170      */
171     protected boolean shouldUpdate(final long lastChecked, final long now, final DatabaseProperties properties,
172                                    String currentVersion) throws UpdateException {
173         //check every 30 days if we know there is an update, otherwise check every 7 days
174         final int checkRange = 30;
175         if (!DateUtil.withinDateRange(lastChecked, now, checkRange)) {
176             LOGGER.debug("Checking web for new version.");
177             final String publishedData = getCurrentReleaseVersion();
178             if (publishedData != null) {
179                 final String[] parts = publishedData.split("\n");
180                 if (parts.length > 1) {
181                     final String message = String.join("\n", Arrays.copyOfRange(parts, 1, parts.length)).trim();
182                     LOGGER.warn("\n\n*********************************************************\n"
183                             + message
184                             + "\n*********************************************************\n");
185                 }
186                 final String currentRelease = parts[0].trim();
187                 final DependencyVersion v = new DependencyVersion(currentRelease);
188                 if (v.getVersionParts() != null && v.getVersionParts().size() >= 3) {
189                     updateToVersion = v.toString();
190                     if (!currentRelease.equals(updateToVersion)) {
191                         properties.save(CURRENT_ENGINE_RELEASE, updateToVersion);
192                     }
193                     properties.save(ENGINE_VERSION_CHECKED_ON, Long.toString(now));
194                 }
195             }
196             LOGGER.debug("Current Release: {}", updateToVersion);
197         }
198         if (updateToVersion == null) {
199             LOGGER.debug("Unable to obtain current release");
200             return false;
201         }
202         final DependencyVersion running = new DependencyVersion(currentVersion);
203         final DependencyVersion released = new DependencyVersion(updateToVersion);
204         if (running.compareTo(released) < 0) {
205             LOGGER.debug("Upgrade recommended");
206             return true;
207         }
208         LOGGER.debug("Upgrade not needed");
209         return false;
210     }
211 
212     /**
213      * Retrieves the current released version number from the github
214      * documentation site.
215      *
216      * @return the current released version number
217      */
218     protected String getCurrentReleaseVersion() {
219         try {
220             final String str = settings.getString(Settings.KEYS.ENGINE_VERSION_CHECK_URL, "https://dependency-check.github.io/DependencyCheck/current.txt");
221             final URL url = new URL(str);
222             String releaseVersion = null;
223             releaseVersion = Downloader.getInstance().fetchContent(url, StandardCharsets.UTF_8);
224             return releaseVersion.trim();
225         } catch (TooManyRequestsException ex) {
226             LOGGER.debug("Unable to retrieve current release version of dependency-check - downloader failed on HTTP 429 Too many requests");
227         } catch (ResourceNotFoundException ex) {
228             LOGGER.debug("Unable to retrieve current release version of dependency-check - downloader  failed on HTTP 404 ResourceNotFound");
229         } catch (MalformedURLException ex) {
230             LOGGER.debug("Unable to retrieve current release version of dependency-check - malformed url?");
231         } catch (IOException ex) {
232             LOGGER.debug("Unable to retrieve current release version of dependency-check - i/o exception");
233         }
234         return null;
235     }
236 
237     @Override
238     public boolean purge(Engine engine) {
239         return true;
240     }
241 }