diff --git a/core/src/main/java/org/owasp/dependencycheck/data/update/cpe/CpeEcosystemCache.java b/core/src/main/java/org/owasp/dependencycheck/data/update/cpe/CpeEcosystemCache.java new file mode 100644 index 00000000000..a9aa1438d20 --- /dev/null +++ b/core/src/main/java/org/owasp/dependencycheck/data/update/cpe/CpeEcosystemCache.java @@ -0,0 +1,79 @@ +/* + * This file is part of dependency-check-core. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright (c) 2020 Jeremy Long. All Rights Reserved. + */ +package org.owasp.dependencycheck.data.update.cpe; + +import com.google.common.base.Strings; +import java.util.HashMap; +import java.util.Map; +import org.owasp.dependencycheck.data.update.nvd.NvdCveParser; +import org.owasp.dependencycheck.utils.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * + * @author Jeremy Long + */ +public final class CpeEcosystemCache { + + private final static String MULTIPLE_ECOSYSTEMS_IDENTIFIED = "MULTIPLE"; + private static Map, String> cache = new HashMap<>(); + private static Map, String> changed = new HashMap<>(); + + private CpeEcosystemCache() { + //empty constructor for utiilty class + } + /** + * The logger. + */ + private static final Logger LOGGER = LoggerFactory.getLogger(NvdCveParser.class); + + public static synchronized String getEcosystem(String vendor, String product, String identifiedEcosystem) { + Pair key = new Pair<>(vendor, product); + String current = cache.get(key); + String result = null; + if (current == null) { + if (!Strings.isNullOrEmpty(identifiedEcosystem)) { + cache.put(key, identifiedEcosystem); + changed.put(key, identifiedEcosystem); + result = identifiedEcosystem; + } + } else if (MULTIPLE_ECOSYSTEMS_IDENTIFIED.equals(current)) { + //do nothing - result is already null + } else if (current.equals(identifiedEcosystem) || identifiedEcosystem == null) { + result = current; + } else { + cache.put(key, MULTIPLE_ECOSYSTEMS_IDENTIFIED); + changed.put(key, MULTIPLE_ECOSYSTEMS_IDENTIFIED); + } + return result; + } + + public static synchronized void setCache(Map, String> cache) { + CpeEcosystemCache.cache = cache; + CpeEcosystemCache.changed = new HashMap<>(); + } + + public static synchronized Map, String> getChanged() { + return CpeEcosystemCache.changed; + } + + public static synchronized boolean isEmpty() { + return CpeEcosystemCache.cache.isEmpty(); + } +} diff --git a/core/src/main/resources/data/dbStatements_mariadb.properties b/core/src/main/resources/data/dbStatements_mariadb.properties index b1d44ec6e7a..998190950fc 100644 --- a/core/src/main/resources/data/dbStatements_mariadb.properties +++ b/core/src/main/resources/data/dbStatements_mariadb.properties @@ -12,7 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -MERGE_PROPERTY=CALL save_property(?, ?) -CLEANUP_ORPHANS=call cleanup_orphans() -UPDATE_ECOSYSTEM=call update_ecosystems() -UPDATE_ECOSYSTEM2=call update_ecosystems2() \ No newline at end of file +MERGE_PROPERTY=CALL save_property(?, ?); +CLEANUP_ORPHANS=call cleanup_orphans(); +UPDATE_ECOSYSTEM=call update_ecosystems(); +UPDATE_ECOSYSTEM2=call update_ecosystems2(); +MERGE_CPE_ECOSYSTEM=call merge_ecosystem(?, ?, ?); \ No newline at end of file diff --git a/core/src/main/resources/data/dbStatements_mysql.properties b/core/src/main/resources/data/dbStatements_mysql.properties index 711dcecc34b..564e3c175be 100644 --- a/core/src/main/resources/data/dbStatements_mysql.properties +++ b/core/src/main/resources/data/dbStatements_mysql.properties @@ -12,7 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -MERGE_PROPERTY=CALL save_property(?, ?) -CLEANUP_ORPHANS=call cleanup_orphans() -UPDATE_ECOSYSTEM=call update_ecosystems() -UPDATE_ECOSYSTEM2=call update_ecosystems2() +MERGE_PROPERTY=CALL save_property(?, ?); +CLEANUP_ORPHANS=call cleanup_orphans(); +UPDATE_ECOSYSTEM=call update_ecosystems(); +UPDATE_ECOSYSTEM2=call update_ecosystems2(); +MERGE_CPE_ECOSYSTEM=call merge_ecosystem(?, ?, ?); diff --git a/core/src/main/resources/data/initialize_mysql.sql b/core/src/main/resources/data/initialize_mysql.sql index aac7369cae2..11c3cc24990 100644 --- a/core/src/main/resources/data/initialize_mysql.sql +++ b/core/src/main/resources/data/initialize_mysql.sql @@ -3,6 +3,7 @@ #DROP database dependencycheck; #CREATE database dependencycheck; + USE dependencycheck; DROP PROCEDURE IF EXISTS dependencycheck.save_property; @@ -11,12 +12,14 @@ DROP PROCEDURE IF EXISTS dependencycheck.update_ecosystems2; DROP PROCEDURE IF EXISTS dependencycheck.cleanup_orphans; DROP PROCEDURE IF EXISTS dependencycheck.update_vulnerability; DROP PROCEDURE IF EXISTS dependencycheck.insert_software; +DROP PROCEDURE IF EXISTS dependencycheck.merge_ecosystem; DROP TABLE IF EXISTS software; DROP TABLE IF EXISTS cpeEntry; DROP TABLE IF EXISTS `reference`; DROP TABLE IF EXISTS properties; DROP TABLE IF EXISTS cweEntry; DROP TABLE IF EXISTS vulnerability; +DROP TABLE IF EXISTS cpeEcosystemCache; CREATE TABLE vulnerability (id int auto_increment PRIMARY KEY, cve VARCHAR(20) UNIQUE, description VARCHAR(8000), v2Severity VARCHAR(20), v2ExploitabilityScore DECIMAL(3,1), @@ -45,6 +48,9 @@ CREATE TABLE software (cveid INT, cpeEntryId INT, versionEndExcluding VARCHAR(50 CREATE TABLE cweEntry (cveid INT, cwe VARCHAR(20), CONSTRAINT fkCweEntry FOREIGN KEY (cveid) REFERENCES vulnerability(id) ON DELETE CASCADE); +CREATE TABLE cpeEcosystemCache (vendor VARCHAR(255), product VARCHAR(255), ecosystem VARCHAR(255), PRIMARY KEY (vendor, product)); +INSERT INTO cpeEcosystemCache (vendor, product, ecosystem) VALUES ('apache', 'zookeeper', 'MULTIPLE'); + CREATE INDEX idxCwe ON cweEntry(cveid); CREATE INDEX idxVulnerability ON vulnerability(cve); CREATE INDEX idxReference ON `reference`(cveid); @@ -69,45 +75,17 @@ DELIMITER ; GRANT EXECUTE ON PROCEDURE dependencycheck.save_property TO 'dcuser'; -DELIMITER // -CREATE PROCEDURE update_ecosystems() -BEGIN -SET @OLD_SQL_SAFE_UPDATES = (SELECT @@SQL_SAFE_UPDATES); -SET SQL_SAFE_UPDATES = 0; -UPDATE cpeEntry n INNER JOIN - (SELECT DISTINCT vendor, product, MIN(ecosystem) eco - FROM cpeEntry - WHERE ecosystem IS NOT NULL - GROUP BY vendor , product) e - ON e.vendor = n.vendor - AND e.product = n.product -SET n.ecosystem = e.eco -WHERE n.ecosystem IS NULL; -SET SQL_SAFE_UPDATES = @OLD_SQL_SAFE_UPDATES; -END // -DELIMITER ; - -GRANT EXECUTE ON PROCEDURE dependencycheck.update_ecosystems TO 'dcuser'; DELIMITER // -CREATE PROCEDURE update_ecosystems2() +CREATE PROCEDURE merge_ecosystem +(IN p_vendor VARCHAR(255), IN p_product VARCHAR(255), IN p_ecosystem varchar(255)) BEGIN -SET @OLD_SQL_SAFE_UPDATES = (SELECT @@SQL_SAFE_UPDATES); -SET SQL_SAFE_UPDATES = 0; -UPDATE cpeEntry e SET e.ecosystem = NULL -WHERE e.id IN (SELECT * FROM - (SELECT DISTINCT entry.id FROM vulnerability v - INNER JOIN software s ON v.id = s.cveid - INNER JOIN cpeEntry r ON s.cpeentryid=r.id - INNER JOIN cpeEntry entry ON r.part = entry.part AND r.vendor = entry.vendor AND r.product = entry.product - WHERE description like '%bindings%' AND r.ecosystem is not null AND entry.ecosystem is not null - UNION ALL - SELECT z.id FROM cpeEntry z WHERE z.ecosystem is not null AND z.vendor = 'apache' AND z.product = 'zookeeper') x); -SET SQL_SAFE_UPDATES = @OLD_SQL_SAFE_UPDATES; +INSERT INTO cpeEcosystemCache (`vendor`, `product`, `ecosystem`) VALUES (p_vendor, p_product, p_ecosystem) + ON DUPLICATE KEY UPDATE `ecosystem`=p_ecosystem; END // DELIMITER ; -GRANT EXECUTE ON PROCEDURE dependencycheck.update_ecosystems2 TO 'dcuser'; +GRANT EXECUTE ON PROCEDURE dependencycheck.merge_ecosystem TO 'dcuser'; DELIMITER // CREATE PROCEDURE cleanup_orphans() @@ -242,4 +220,39 @@ DELIMITER ; GRANT EXECUTE ON PROCEDURE dependencycheck.insert_software TO 'dcuser'; +DELIMITER // +CREATE PROCEDURE update_ecosystems() +BEGIN + SET @OLD_SQL_SAFE_UPDATES = (SELECT @@SQL_SAFE_UPDATES); + SET SQL_SAFE_UPDATES = 0; + UPDATE cpeEntry e INNER JOIN cpeEcosystemCache c + ON c.vendor=e.vendor + AND c.product=e.product + SET e.ecosystem=c.ecosystem + WHERE e.ecosystem IS NULL AND c.ecosystem<>'MULTIPLE'; + + SET SQL_SAFE_UPDATES = @OLD_SQL_SAFE_UPDATES; +END // +DELIMITER ; + +GRANT EXECUTE ON PROCEDURE dependencycheck.update_ecosystems TO 'dcuser'; + +DELIMITER // +CREATE PROCEDURE update_ecosystems2() +BEGIN + SET @OLD_SQL_SAFE_UPDATES = (SELECT @@SQL_SAFE_UPDATES); + SET SQL_SAFE_UPDATES = 0; + UPDATE cpeEntry e INNER JOIN cpeEcosystemCache c + ON c.vendor=e.vendor + AND c.product=e.product + SET e.ecosystem=null + WHERE c.ecosystem='MULTIPLE' + AND e.ecosystem IS NOT NULL; + + SET SQL_SAFE_UPDATES = @OLD_SQL_SAFE_UPDATES; +END // +DELIMITER ; + +GRANT EXECUTE ON PROCEDURE dependencycheck.update_ecosystems2 TO 'dcuser'; + INSERT INTO properties(id, value) VALUES ('version', '5.0'); diff --git a/core/src/test/java/org/owasp/dependencycheck/data/update/cpe/CpeEcosystemCacheTest.java b/core/src/test/java/org/owasp/dependencycheck/data/update/cpe/CpeEcosystemCacheTest.java new file mode 100644 index 00000000000..e0afb14a505 --- /dev/null +++ b/core/src/test/java/org/owasp/dependencycheck/data/update/cpe/CpeEcosystemCacheTest.java @@ -0,0 +1,146 @@ +/* + * This file is part of dependency-check-core. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright (c) 2020 Jeremy Long. All Rights Reserved. + */ +package org.owasp.dependencycheck.data.update.cpe; + +import java.util.HashMap; +import java.util.Map; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import static org.junit.Assert.*; +import org.owasp.dependencycheck.utils.Pair; + +/** + * + * @author Jeremy Long + */ +public class CpeEcosystemCacheTest { + + @Before + public void setUp() { + + } + + @After + public void tearDown() { + } + + /** + * Test of getEcosystem method, of class CpeEcosystemCache. + */ + @Test + public void testGetEcosystem() { + Pair key = new Pair<>("apache", "zookeeper"); + Map, String> map = new HashMap<>(); + map.put(key, "java"); + CpeEcosystemCache.setCache(map); + + String expected = "java"; + String result = CpeEcosystemCache.getEcosystem("apache", "zookeeper", null); + assertEquals(expected, result); + + expected = "MULTIPLE"; + result = CpeEcosystemCache.getEcosystem("apache", "zookeeper", "c++"); + assertEquals(expected, result); + + result = CpeEcosystemCache.getEcosystem("pivotal", "spring-framework", null); + assertNull(result); + + expected = "java"; + result = CpeEcosystemCache.getEcosystem("pivotal", "spring-framework", "java"); + assertEquals(expected, result); + + expected = "java"; + result = CpeEcosystemCache.getEcosystem("pivotal", "spring-framework", "java"); + assertEquals(expected, result); + + result = CpeEcosystemCache.getEcosystem("microsoft", "word", null ); + assertNull(result); + + result = CpeEcosystemCache.getEcosystem("microsoft", "word", null ); + assertNull(result); + + result = CpeEcosystemCache.getEcosystem("microsoft", "word", "" ); + assertNull(result); + } + + /** + * Test of setCache method, of class CpeEcosystemCache. + */ + @Test + public void testSetCache() { + Map, String> map = new HashMap<>(); + CpeEcosystemCache.setCache(map); + assertTrue(CpeEcosystemCache.isEmpty()); + + map = new HashMap<>(); + Pair key = new Pair<>("apache", "zookeeper"); + map.put(key, "java"); + CpeEcosystemCache.setCache(map); + + assertFalse(CpeEcosystemCache.isEmpty()); + } + + /** + * Test of getChanged method, of class CpeEcosystemCache. + */ + @Test + public void testGetChanged() { + Pair key = new Pair<>("apache", "zookeeper"); + Map, String> map = new HashMap<>(); + map.put(key, "java"); + CpeEcosystemCache.setCache(map); + + Map, String> result = CpeEcosystemCache.getChanged(); + assertTrue(result.isEmpty()); + + CpeEcosystemCache.getEcosystem("apache", "zookeeper", "java"); + + result = CpeEcosystemCache.getChanged(); + assertTrue(result.isEmpty()); + + CpeEcosystemCache.getEcosystem("apache", "zookeeper", null); + + result = CpeEcosystemCache.getChanged(); + assertTrue(result.isEmpty()); + + CpeEcosystemCache.getEcosystem("apache", "zookeeper", "c++"); + + result = CpeEcosystemCache.getChanged(); + assertFalse(result.isEmpty()); + } + + /** + * Test of isEmpty method, of class CpeEcosystemCache. + */ + @Test + public void testIsEmpty() { + Map, String> map = new HashMap<>(); + CpeEcosystemCache.setCache(map); + boolean expResult = true; + boolean result = CpeEcosystemCache.isEmpty(); + assertEquals(expResult, result); + map.put(new Pair("apache", "zookeeper"), "MULTPILE"); + expResult = false; + result = CpeEcosystemCache.isEmpty(); + assertEquals(expResult, result); + } + +}