EngineVersionCheck.java

  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. import java.io.IOException;
  20. import java.net.MalformedURLException;
  21. import java.net.URL;
  22. import java.nio.charset.StandardCharsets;
  23. import java.util.Arrays;
  24. import javax.annotation.concurrent.ThreadSafe;

  25. import org.owasp.dependencycheck.Engine;
  26. import org.owasp.dependencycheck.data.nvdcve.CveDB;
  27. import org.owasp.dependencycheck.data.nvdcve.DatabaseException;
  28. import org.owasp.dependencycheck.data.nvdcve.DatabaseProperties;
  29. import org.owasp.dependencycheck.data.update.exception.UpdateException;
  30. import org.owasp.dependencycheck.utils.DateUtil;
  31. import org.owasp.dependencycheck.utils.DependencyVersion;
  32. import org.owasp.dependencycheck.utils.Downloader;
  33. import org.owasp.dependencycheck.utils.ResourceNotFoundException;
  34. import org.owasp.dependencycheck.utils.Settings;
  35. import org.owasp.dependencycheck.utils.TooManyRequestsException;
  36. import org.slf4j.Logger;
  37. import org.slf4j.LoggerFactory;

  38. /**
  39.  * Checks the gh-pages dependency-check site to determine the current released
  40.  * version number. If the released version number is greater than the running
  41.  * version number a warning is printed recommending that an upgrade be
  42.  * performed.
  43.  *
  44.  * @author Jeremy Long
  45.  */
  46. @ThreadSafe
  47. public class EngineVersionCheck implements CachedWebDataSource {

  48.     /**
  49.      * Static logger.
  50.      */
  51.     private static final Logger LOGGER = LoggerFactory.getLogger(EngineVersionCheck.class);
  52.     /**
  53.      * The property key indicating when the last version check occurred.
  54.      */
  55.     public static final String ENGINE_VERSION_CHECKED_ON = "VersionCheckOn";
  56.     /**
  57.      * The property key indicating when the last version check occurred.
  58.      */
  59.     public static final String CURRENT_ENGINE_RELEASE = "CurrentEngineRelease";
  60.     /**
  61.      * The version retrieved from the database properties or web to check
  62.      * against.
  63.      */
  64.     private String updateToVersion;
  65.     /**
  66.      * The configured settings.
  67.      */
  68.     private Settings settings;

  69.     /**
  70.      * Constructs a new engine version check utility for testing.
  71.      *
  72.      * @param settings the configured settings
  73.      */
  74.     protected EngineVersionCheck(Settings settings) {
  75.         this.settings = settings;
  76.     }

  77.     /**
  78.      * Constructs a new engine version check utility.
  79.      */
  80.     public EngineVersionCheck() {
  81.     }

  82.     /**
  83.      * Getter for updateToVersion - only used for testing. Represents the
  84.      * version retrieved from the database.
  85.      *
  86.      * @return the version to test
  87.      */
  88.     protected String getUpdateToVersion() {
  89.         return updateToVersion;
  90.     }

  91.     /**
  92.      * Setter for updateToVersion - only used for testing. Represents the
  93.      * version retrieved from the database.
  94.      *
  95.      * @param version the version to test
  96.      */
  97.     protected void setUpdateToVersion(String version) {
  98.         updateToVersion = version;
  99.     }

  100.     /**
  101.      * Downloads the current released version number and compares it to the
  102.      * running engine's version number. If the released version number is newer
  103.      * a warning is printed recommending an upgrade.
  104.      *
  105.      * @return returns false as no updates are made to the database that would
  106.      * require compaction
  107.      * @throws UpdateException thrown if the local database properties could not
  108.      * be updated
  109.      */
  110.     @Override
  111.     public boolean update(Engine engine) throws UpdateException {
  112.         this.settings = engine.getSettings();
  113.         try {
  114.             final CveDB db = engine.getDatabase();
  115.             final boolean autoupdate = settings.getBoolean(Settings.KEYS.AUTO_UPDATE, true);
  116.             final boolean enabled = settings.getBoolean(Settings.KEYS.UPDATE_VERSION_CHECK_ENABLED, true);
  117.             final String datafeed = settings.getString(Settings.KEYS.NVD_API_DATAFEED_URL);
  118.             /*
  119.              * Only update if auto-update is enabled, the engine check is
  120.              * enabled, and the NVD DataFeed is being used (i.e. the user
  121.              * is likely on a private network). This check is not really needed
  122.              * so we are okay skipping it.
  123.              */
  124.             if (enabled && autoupdate && datafeed != null) {
  125.                 LOGGER.debug("Begin Engine Version Check");

  126.                 final DatabaseProperties properties = db.getDatabaseProperties();

  127.                 final long lastChecked = DateUtil.getEpochValueInSeconds(properties.getProperty(ENGINE_VERSION_CHECKED_ON, "0"));
  128.                 final long now = System.currentTimeMillis() / 1000;
  129.                 updateToVersion = properties.getProperty(CURRENT_ENGINE_RELEASE, "");
  130.                 final String currentVersion = settings.getString(Settings.KEYS.APPLICATION_VERSION, "0.0.0");
  131.                 LOGGER.debug("Last checked: {}", lastChecked);
  132.                 LOGGER.debug("Now: {}", now);
  133.                 LOGGER.debug("Current version: {}", currentVersion);
  134.                 final boolean updateNeeded = shouldUpdate(lastChecked, now, properties, currentVersion);
  135.                 if (updateNeeded) {
  136.                     LOGGER.warn("A new version of dependency-check is available. Consider updating to version {}.",
  137.                             updateToVersion);
  138.                 }
  139.             }
  140.         } catch (DatabaseException ex) {
  141.             LOGGER.debug("Database Exception opening databases to retrieve properties", ex);
  142.             throw new UpdateException("Error occurred updating database properties.");
  143.         }
  144.         return false;
  145.     }

  146.     /**
  147.      * Determines if a new version of the dependency-check engine has been
  148.      * released.
  149.      *
  150.      * @param lastChecked the epoch time of the last version check
  151.      * @param now the current epoch time
  152.      * @param properties the database properties object
  153.      * @param currentVersion the current version of dependency-check
  154.      * @return <code>true</code> if a newer version of the database has been
  155.      * released; otherwise <code>false</code>
  156.      * @throws UpdateException thrown if there is an error connecting to the
  157.      * github documentation site or accessing the local database.
  158.      */
  159.     protected boolean shouldUpdate(final long lastChecked, final long now, final DatabaseProperties properties,
  160.                                    String currentVersion) throws UpdateException {
  161.         //check every 30 days if we know there is an update, otherwise check every 7 days
  162.         final int checkRange = 30;
  163.         if (!DateUtil.withinDateRange(lastChecked, now, checkRange)) {
  164.             LOGGER.debug("Checking web for new version.");
  165.             final String publishedData = getCurrentReleaseVersion();
  166.             if (publishedData != null) {
  167.                 final String[] parts = publishedData.split("\n");
  168.                 if (parts.length > 1) {
  169.                     final String message = String.join("\n", Arrays.copyOfRange(parts, 1, parts.length)).trim();
  170.                     LOGGER.warn("\n\n*********************************************************\n"
  171.                             + message
  172.                             + "\n*********************************************************\n");
  173.                 }
  174.                 final String currentRelease = parts[0].trim();
  175.                 final DependencyVersion v = new DependencyVersion(currentRelease);
  176.                 if (v.getVersionParts() != null && v.getVersionParts().size() >= 3) {
  177.                     updateToVersion = v.toString();
  178.                     if (!currentRelease.equals(updateToVersion)) {
  179.                         properties.save(CURRENT_ENGINE_RELEASE, updateToVersion);
  180.                     }
  181.                     properties.save(ENGINE_VERSION_CHECKED_ON, Long.toString(now));
  182.                 }
  183.             }
  184.             LOGGER.debug("Current Release: {}", updateToVersion);
  185.         }
  186.         if (updateToVersion == null) {
  187.             LOGGER.debug("Unable to obtain current release");
  188.             return false;
  189.         }
  190.         final DependencyVersion running = new DependencyVersion(currentVersion);
  191.         final DependencyVersion released = new DependencyVersion(updateToVersion);
  192.         if (running.compareTo(released) < 0) {
  193.             LOGGER.debug("Upgrade recommended");
  194.             return true;
  195.         }
  196.         LOGGER.debug("Upgrade not needed");
  197.         return false;
  198.     }

  199.     /**
  200.      * Retrieves the current released version number from the github
  201.      * documentation site.
  202.      *
  203.      * @return the current released version number
  204.      */
  205.     protected String getCurrentReleaseVersion() {
  206.         try {
  207.             final String str = settings.getString(Settings.KEYS.ENGINE_VERSION_CHECK_URL, "https://jeremylong.github.io/DependencyCheck/current.txt");
  208.             final URL url = new URL(str);
  209.             String releaseVersion = null;
  210.             releaseVersion = Downloader.getInstance().fetchContent(url, StandardCharsets.UTF_8);
  211.             return releaseVersion.trim();
  212.         } catch (TooManyRequestsException ex) {
  213.             LOGGER.debug("Unable to retrieve current release version of dependency-check - downloader failed on HTTP 429 Too many requests");
  214.         } catch (ResourceNotFoundException ex) {
  215.             LOGGER.debug("Unable to retrieve current release version of dependency-check - downloader  failed on HTTP 404 ResourceNotFound");
  216.         } catch (MalformedURLException ex) {
  217.             LOGGER.debug("Unable to retrieve current release version of dependency-check - malformed url?");
  218.         } catch (IOException ex) {
  219.             LOGGER.debug("Unable to retrieve current release version of dependency-check - i/o exception");
  220.         }
  221.         return null;
  222.     }

  223.     @Override
  224.     public boolean purge(Engine engine) {
  225.         return true;
  226.     }
  227. }