diff --git a/alerts/alert_sender.py b/alerts/alert_sender.py new file mode 100644 index 0000000..82d33e6 --- /dev/null +++ b/alerts/alert_sender.py @@ -0,0 +1,104 @@ +import smtplib +import ssl +import traceback +import config +import logger +import gnupg +from email.mime.text import MIMEText + + +class EmailSender: + + def __init__(self): + self.host = config.get_smtp_server_host() + self.port = config.get_smtp_server_port() + self.user = config.get_smtp_user() + self.password = config.get_smtp_password() + self.sender = config.get_smtp_sender() + self.receiver = config.get_smtp_receiver() + self.smtp_client = None + + def send(self, email_body, subject='IVA Alert'): + email = self.create_email(subject, email_body) + return self.send_email(email) + + def create_email(self, subject, body): + msg = create_mime_obj(body) + msg['Subject'] = subject + msg['From'] = self.sender + msg['To'] = self.receiver + return msg + + def send_email(self, email): + logger.info('SMTP - trying to send email') + try: + self.create_smtp_client() + self.smtp_login() + self.smtp_send(email) + self.smtp_client.quit() + logger.info('SMTP - email successfully sent') + return True + except Exception: + logger.error('SMTP - unable to send email') + logger.error('SMTP - ' + str(traceback.format_exc())) + return False + + def create_smtp_client(self): + if config.is_smtps_enabled(): + self.smtp_client = smtplib.SMTP_SSL(self.host, self.port, context=create_ssl_context()) + else: + self.smtp_client = smtplib.SMTP(self.host, self.port) + self.starttls() + + def starttls(self): + if config.is_smtp_starttls_enabled(): + try: + self.smtp_client.ehlo() + self.smtp_client.starttls(context=create_ssl_context()) + except smtplib.SMTPHeloError: + logger.error('SMTP - The server did not reply properly to the HELO greeting') + except smtplib.SMTPException: + logger.error('SMTP - The server does not support the STARTTLS extension') + except RuntimeError: + logger.error('SMTP - SSL/TLS support is not available to your Python interpreter') + + def smtp_login(self): + self.smtp_client.login(self.user, self.password) + + def smtp_send(self, email): + self.smtp_client.sendmail(self.sender, [self.receiver], email.as_string()) + + +def create_ssl_context(): + if config.is_verify_smtp_server_cert_enabled(): + return ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH, cafile=config.get_smtp_ca_cert_file()) + return None + + +def create_mime_obj(body): + if config.is_gpg_encryption_enabled(): + return MIMEText(encrypt_body(body)) + return MIMEText(body) + + +def encrypt_body(body): + gnu = create_gpg_obj() + import_keys(gnu) + return encrypt(body, gnu) + + +def create_gpg_obj(): + return gnupg.GPG(homedir=config.get_gpg_home_dir()) + + +def encrypt(body, gpg): + return str(gpg.encrypt(body, get_pub_key_fingerprint(gpg))) + + +def import_keys(gnu): + with open(config.get_gpg_pub_key_file(), 'r') as f: + gnu.import_keys(f.read()) + + +def get_pub_key_fingerprint(gnu): + return gnu.list_keys()[0].get('fingerprint') diff --git a/alerts/alerts.py b/alerts/alerts.py new file mode 100644 index 0000000..f1d307d --- /dev/null +++ b/alerts/alerts.py @@ -0,0 +1,201 @@ +import logger +import datetime +from database import Database +from wfn.encoding import Decoder +from collections import namedtuple +from alerts.alert_sender import EmailSender +from inventory.inventory import INVENTORY_DB_COLLECTION +from alerts.alerts_logger import generate_log_entry_for_added_cve, generate_log_entry_for_new_alert +from alerts.alerts_logger import generate_log_entry_for_removed_cve, generate_log_entry_for_changed_alert_status + +ALERTS_DB_COLLECTION = 'alerts' +STATUS = namedtuple('STATUS', ['new', 'sent', 'closed', 'removed'])('new', 'sent', 'closed', 'removed') + + +class Alerts: + + def __init__(self): + self.db = Database() + + def insert_alert(self, software_id, cve_id): + if not self.alert_for_software_exists(software_id): + self.insert_new_alert_for_inventory_item(software_id, cve_id) + else: + self.add_new_cve_to_alert(software_id, cve_id) + + def alert_for_software_exists(self, software_id): + return self.db.exist_doc_in_collection(get_id_as_dict(software_id), ALERTS_DB_COLLECTION) + + def insert_new_alert_for_inventory_item(self, item_id, cve_id): + self.db.insert_document_in_collection(create_new_alert_dict(item_id, cve_id), ALERTS_DB_COLLECTION) + + def add_new_cve_to_alert(self, item_id, new_cve): + alert = self.get_software_alert(item_id) + self.update_alert(item_id, generate_update_for_add_new_cve(alert, new_cve)) + self.change_status_to_new(alert, item_id) + + def change_status_to_new(self, alert, item_id): + if not is_status_new(alert): + self.change_alert_status(item_id, STATUS.new) + + def remove_cve_from_alert(self, item_id, cve): + alert = self.get_software_alert(item_id) + self.update_alert(item_id, generate_update_for_remove_cve(alert, cve)) + self.change_status_to_removed(alert, item_id) + + def change_status_to_removed(self, alert, item_id): + if len(alert.get('cves')) == 0: + self.change_alert_status(item_id, STATUS.removed) + + def change_alert_status(self, software_id, new_status): + alert = self.get_software_alert(software_id) + if can_status_be_changed(alert, new_status): + self.removed_cves_from_alert(software_id, alert, new_status) + self.update_alert(software_id, generate_update_for_change_status_alert(alert, new_status)) + return False + + def removed_cves_from_alert(self, software_id, alert, new_status): + if new_status == STATUS.removed: + for cve in alert.get('cves'): + self.remove_cve_from_alert(software_id, cve) + + def update_alert(self, item_id, update): + self.db.update_document_in_collection(get_id_as_dict(item_id), update, ALERTS_DB_COLLECTION) + + def get_software_alert(self, software_id): + return self.db.search_document_in_collection(get_id_as_dict(software_id), ALERTS_DB_COLLECTION) + + def get_alerts(self): + alerts = [] + alerts_status_new = self.get_alerts_of_status_sorted_by_date(STATUS.new) + alerts_status_sent = self.get_alerts_of_status_sorted_by_date(STATUS.sent) + alerts_status_closed = self.get_alerts_of_status_sorted_by_date(STATUS.closed) + alerts_status_removed = self.get_alerts_of_status_sorted_by_date(STATUS.removed) + alerts.extend(alerts_status_new) + alerts.extend(alerts_status_sent) + alerts.extend(alerts_status_closed) + alerts.extend(alerts_status_removed) + return alerts + + def get_alerts_of_status_sorted_by_date(self, status): + return list(sort_alerts_by_date(self.db.search_documents_in_collection({'status': status}, ALERTS_DB_COLLECTION))) + + def update_notes(self, software_id, notes): + self.db.update_document_in_collection(get_id_as_dict(software_id), {'notes': notes}, ALERTS_DB_COLLECTION) + + def get_number_of_new_alerts(self): + return self.get_number_of_alerts_by_status(STATUS.new) + + def get_number_of_sent_alerts(self): + return self.get_number_of_alerts_by_status(STATUS.sent) + + def get_number_of_alerts_by_status(self, status): + return self.db.get_number_of_documents_in_collection(ALERTS_DB_COLLECTION, {'status': status}) + + def send_sw_alert_by_email(self, software_id): + software = self.get_software(software_id) + alert = self.get_software_alert(software_id) + sw_alert_email = create_sw_alert_email(alert, software) + return self.send(sw_alert_email, software) + + def send(self, alert_mail, software): + sw_string = software.get('product') + ' ' + software.get('product') + ' ' + software.get('version') + logger.info('ALERTS - sending notification for ' + sw_string) + was_sent = EmailSender().send(alert_mail) + if was_sent: + logger.info('ALERTS - notification for ' + sw_string + ' successfully sent') + self.change_alert_status(software.get('id'), new_status=STATUS.sent) + return True + logger.error('ALERTS - failed to sent notification for ' + sw_string) + return False + + def get_software(self, software_id): + return self.db.search_document_in_collection({'id': software_id}, INVENTORY_DB_COLLECTION) + + +def create_new_alert_dict(software_id, cve_id): + return {'generated_on': datetime.datetime.utcnow(), + 'software_id': software_id, + 'cves': [cve_id], + 'status': STATUS.new, + 'log': [generate_log_entry_for_new_alert(cve_id)], + 'notes': ''} + + +def get_id_as_dict(item_id): + return {'software_id': item_id} + + +def update_log(alert, log_entry): + log = alert.get('log') + log.append(log_entry) + return log + + +def update_cves(alert, cve, option): + cves = alert.get('cves') + if option == 'append': + cves.append(cve) + elif option == 'remove': + cves.remove(cve) + return cves + + +def sort_alerts_by_date(alerts): + return alerts.sort('generated_on', 1) + + +def can_status_be_changed(alert, new_status): + if alert is not None: + if (new_status == 'new' and len(alert.get('cves')) == 0) and current_status_close_or_removed(alert.get('status')): + return False + return True + return False + + +def current_status_close_or_removed(status): + return status == STATUS.closed or status == STATUS.removed + + +def generate_update_for_add_new_cve(alert, new_cve): + return {'cves': update_cves(alert, new_cve, 'append'), + 'log': update_log(alert, generate_log_entry_for_added_cve(new_cve))} + + +def generate_update_for_remove_cve(alert, cve): + return {'cves': update_cves(alert, cve, 'remove'), + 'log': update_log(alert, generate_log_entry_for_removed_cve(cve))} + + +def generate_update_for_change_status_alert(alert, new_status): + return {'status': new_status, + 'log': update_log(alert, generate_log_entry_for_changed_alert_status(alert.get('status'), new_status))} + + +def is_status_new(alert): + return alert.get('status') == STATUS.new + + +def create_sw_alert_email(alert, software): + email = 'Generated on: ' + str(alert.get('generated_on')) + '\n\n' \ + 'Software ID: ' + software.get('id') + '\n\n' \ + 'Product: ' + software.get('product') + '\n\n' \ + 'Vendor: ' + software.get('vendor') + '\n\n' \ + 'Version: ' + software.get('version') + '\n\n' \ + 'CPE: ' + get_software_cpe(software) + '\n\n' \ + 'CVEs: ' + str(alert.get('cves')) + '\n\n' \ + 'Status: ' + alert.get('status') + '\n\n' \ + 'Log:\n' + format_log(alert.get('log')) + '\n' \ + 'Notes: ' + alert.get('notes') + return email + + +def get_software_cpe(software): + return Decoder.decode_non_alphanumeric_characters((software.get('cpe').get('uri_binding'))) + + +def format_log(log): + log_str = '' + for entry in log: + log_str += str(entry.get('date')) + ': '+entry.get('event')+'\n' + return log_str diff --git a/alerts/alerts_logger.py b/alerts/alerts_logger.py new file mode 100644 index 0000000..4ba626d --- /dev/null +++ b/alerts/alerts_logger.py @@ -0,0 +1,27 @@ +import datetime + + +def generate_log_entry_for_new_alert(cve): + return generate_log_entry('Alert generated due to ' + cve) + + +def generate_log_entry_for_added_cve(cve): + return generate_log_entry(cve + ' was added') + + +def generate_log_entry_for_removed_cve(cve): + return generate_log_entry(cve + ' was removed') + + +def generate_log_entry_for_changed_alert_status(old_status, new_status): + return generate_log_entry('Alert status changed: ' + old_status + ' to ' + new_status) + + +def generate_log_entry(event): + return {'date': get_date(), 'event': event} + + +def get_date(): + return datetime.datetime.utcnow() + + diff --git a/config.ini b/config.ini new file mode 100644 index 0000000..0746d22 --- /dev/null +++ b/config.ini @@ -0,0 +1,52 @@ +[test-section] +test-option=test option + +[database] +host=localhost +port=27017 +name=iva +authentication=0 +user=user +password=password + +[inventory-database] +host=localhost +user=root +password=123 +name=glpi + +[frontend] +host=192.168.56.125 +port=8080 + +[cve-search] +dir=/home/luis/iva_deb/iva/cve_search +db=cvedb +url=http://192.168.56.125:5000/cve/ + +[smtp] +host=172.17.0.2 +port=25 +user=test +password=123 +sender=iva +receiver=test@localdomain +smtps=0 +starttls=1 +verify_server_cert=0 +ca_cert_file=/usr/local/share/iva/ssl/smtp/ca_cert.pem + +[gpg] +required=0 +home_dir=/usr/local/share/iva/gpg +pub_key_file=/usr/local/share/iva/gpg/0xF4G24G5Q.asc + +[ldap] +host=192.168.1.1 +port=389 +base_dn=ou=users,dc=honeynet,dc=de +tls=dsdsd +cacert=/path/to/cacert.pem + +[logging] +file=iva.log \ No newline at end of file diff --git a/config.py b/config.py new file mode 100644 index 0000000..c8f0e69 --- /dev/null +++ b/config.py @@ -0,0 +1,181 @@ +from urllib.parse import urljoin +import configparser +import os +import logger + +config_parser = None + + +def load_configuration_from_file(file_path): + global config_parser + if config_parser is None: + if os.path.exists(file_path): + config_parser = configparser.ConfigParser() + config_parser.read(file_path) + logger.info('configuration file was read from ' + file_path) + else: + logger.info('unable to read configuration file: ' + file_path) + + +def reload_configuration(file_path): + global config_parser + config_parser = None + load_configuration_from_file(file_path) + + +def get_database_host(): + return config_parser.get('database', 'host') + + +def get_database_port(): + return config_parser.getint('database', 'port') + + +def get_database_name(): + return config_parser.get('database', 'name') + + +def get_database_user(): + return config_parser.get('database', 'user') + + +def get_database_password(): + return config_parser.get('database', 'password') + + +def is_database_authentication_enabled(): + try: + return config_parser.getboolean('database', 'authentication') + except ValueError: + return False + + +def get_cve_search_db_name(): + return config_parser.get('cve-search', 'db') + + +def get_cve_search_dir(): + return config_parser.get('cve-search', 'dir') + + +def get_cve_search_url(): + return add_path_to_cve_search_url(config_parser.get('cve-search', 'url')) + + +def add_path_to_cve_search_url(url): + return urljoin(url, '/cve/') + + +def get_log_file(): + return config_parser.get('logging', 'file') + + +def get_frontend_host(): + return config_parser.get('frontend', 'host') + + +def get_frontend_port(): + return config_parser.get('frontend', 'port') + + +def get_inventory_database_host(): + return config_parser.get('inventory-database', 'host') + + +def get_inventory_database_user(): + return config_parser.get('inventory-database', 'user') + + +def get_inventory_database_password(): + return config_parser.get('inventory-database', 'password') + + +def get_glpi_db_name(): + return config_parser.get('inventory-database', 'name') + + +def get_smtp_server_host(): + return config_parser.get('smtp', 'host') + + +def get_smtp_server_port(): + return config_parser.get('smtp', 'port') + + +def get_smtp_user(): + return config_parser.get('smtp', 'user') + + +def get_smtp_password(): + return config_parser.get('smtp', 'password') + + +def get_smtp_sender(): + return config_parser.get('smtp', 'sender') + + +def get_smtp_receiver(): + return config_parser.get('smtp', 'receiver') + + +def is_smtp_starttls_enabled(): + try: + return config_parser.getboolean('smtp', 'starttls') + except ValueError: + return False + + +def is_smtps_enabled(): + try: + return config_parser.getboolean('smtp', 'smtps') + except ValueError: + return False + + +def is_verify_smtp_server_cert_enabled(): + try: + return config_parser.getboolean('smtp', 'verify_server_cert') + except ValueError: + return False + + +def get_smtp_ca_cert_file(): + return config_parser.get('smtp', 'ca_cert_file') + + +def is_gpg_encryption_enabled(): + try: + return config_parser.getboolean('gpg', 'required') + except ValueError: + return False + + +def get_gpg_home_dir(): + return config_parser.get('gpg', 'home_dir') + + +def get_gpg_pub_key_file(): + return config_parser.get('gpg', 'pub_key_file') + + +def get_ldap_host(): + return config_parser.get('ldap', 'host') + + +def get_ldap_port(): + return config_parser.getint('ldap', 'port') + + +def get_ldap_base_dn(): + return config_parser.get('ldap', 'base_dn') + + +def get_ldap_cacert_file_path(): + return config_parser.get('ldap', 'cacert') + + +def is_ldap_tls_enabled(): + try: + return config_parser.getboolean('ldap', 'tls') + except ValueError: + return False diff --git a/database.py b/database.py new file mode 100644 index 0000000..6eed80d --- /dev/null +++ b/database.py @@ -0,0 +1,99 @@ +from pymongo import MongoClient +import config + + +class Database: + + def __init__(self, db_name=None): + self.mongodb_client = create_mongodb_client() + self.db = self.create_db(db_name) + self.authenticate_user() + + def create_db(self, db_name): + if db_name is None: + return self.mongodb_client[config.get_database_name()] + return self.mongodb_client[db_name] + + def authenticate_user(self): + if config.is_database_authentication_enabled(): + self.db.authenticate(config.get_database_user(), config.get_database_password()) + + def insert_document_in_collection(self, doc, collection_name): + collection = self.db[collection_name] + collection.insert_one(doc) + + def exist_doc_in_collection(self, search_condition, collection_name): + collection = self.db[collection_name] + query_result = collection.find(search_condition).limit(1) + return doc_found(query_result) + + def search_text_with_regex_in_collection(self, regex, field, collection_name): + collection = self.db[collection_name] + return collection.find({field: get_regex_dict(regex)}) + + def search_text_with_regex_in_collection_mul(self, regex_a, regex_b, field_a, field_b, collection_name): + collection = self.db[collection_name] + return collection.find({'$and': [{field_a: get_regex_dict(regex_a)}, {field_b: get_regex_dict(regex_b)}]}) + + def search_document_in_collection(self, search_condition, collection_name): + collection = self.db[collection_name] + return collection.find_one(search_condition, {'_id': 0}) + + def search_documents_in_collection(self, search_condition, collection_name): + collection = self.db[collection_name] + return collection.find(search_condition, {'_id': 0}) + + def search_documents_and_aggregate(self, search_condition, aggregation, collection_name): + collection = self.db[collection_name] + return list(collection.aggregate([{'$match': search_condition}, {'$project': aggregation}])) + + def get_number_of_documents_in_collection(self, collection_name, filter_=None): + collection = self.db[collection_name] + return collection.count(filter_) + + def update_document_in_collection(self, filter_, update, collection_name, insert_if_not_exists=False): + collection = self.db[collection_name] + collection.update_one(filter_, {'$set': update}, upsert=insert_if_not_exists) + + def update_documents_in_collection(self, docs, find_filter, collection_name): + if len(docs) > 0: + bulk = self.db[collection_name].initialize_ordered_bulk_op() + for doc in docs: + bulk.find({find_filter: doc.get(find_filter)}).upsert().update({'$set': doc}) + bulk.execute() + + def get_documents_from_collection(self, collection_name): + collection = self.db[collection_name] + return list(collection.find({}, {'_id': 0})) + + def get_documents_from_collection_in_range(self, collection_name, skip=0, limit=0): + collection = self.db[collection_name] + return list(collection.find({}, {'_id': 0}).skip(skip).limit(limit)) + + def delete_document_from_collection(self, query, collection_name): + collection = self.db[collection_name] + collection.delete_one(query) + + def close(self): + self.mongodb_client.close() + + def drop_collection(self, collection_name): + collection = self.db[collection_name] + collection.drop() + + def insert_documents_in_collection(self, documents, collection_name): + collection = self.db[collection_name] + collection.insert_many(documents=documents) + +def create_mongodb_client(): + return MongoClient(config.get_database_host(), config.get_database_port()) + + +def doc_found(query_result): + found = query_result.count() > 0 + query_result.close() + return found + + +def get_regex_dict(regex): + return {'$regex': regex} \ No newline at end of file diff --git a/dummy/config.ini b/dummy/config.ini new file mode 100644 index 0000000..5e3fe43 --- /dev/null +++ b/dummy/config.ini @@ -0,0 +1,49 @@ +[database] +host=localhost +port=27017 +name=iva_dummy +authentication=0 +user=user +password=password + +[inventory-database] +host=localhost +user=user +password=pass +name=glpi + +[frontend] +host=localhost +port=8080 + +[cve-search] +dir=cve_search_nstallation_path +db=cvedb +url=http://localhost:5000/ + +[smtp] +host=host +port=25 +user=user +password=pass +sender=receiver@test +receiver=receiver@test +smtps=1 +starttls=0 +verify_server_cert=1 +ca_cert_file=/usr/local/share/iva/ssl/smtp/ca_cert.pem + +[gpg] +required=1 +home_dir=/usr/local/share/iva/gpg +pub_key_file=/usr/local/share/iva/gpg/0xF4G24G5Q.asc + +[ldap] +host=host +port=389 +base_dn=ou=users,dc=test,dc=com +tls=0 +cacert=/path/to/cacert.pem + +[logging] +file=dummy/dummy_iva.log \ No newline at end of file diff --git a/dummy/dummy_db.py b/dummy/dummy_db.py new file mode 100644 index 0000000..741793f --- /dev/null +++ b/dummy/dummy_db.py @@ -0,0 +1,43 @@ +from collections import namedtuple +from database import Database + + +INVENTORY = [{'vendor' : 'Microsoft Corporation', 'product' : 'Microsoft .NET Framework 4.5.2', 'cpe':None, 'version' : '4.5.51209', 'id' : 'ef6d303afe3e21af0c9274c0cf45e7e2', 'cve_matches' : []}, + {"vendor" : "Microsoft Corporation", "product" : "Internet Explorer", "cpe" : { "wfn" : { "other" : "ANY", "sw_edition" : "ANY", "product" : "internet_explorer", "part" : "a", "update" : "ANY", "target_hw" : "ANY", "version" : "8.*", "vendor" : "microsoft", "language" : "ANY", "edition" : "ANY", "target_sw" : "ANY"}, "uri_binding": "cpe:/a:microsoft:internet_explorer:8.%2a"}, "version": "8.0.7601.17514", "id": "d6218a56203853300f4862ae8c23a103", "cve_matches": []}, + {"vendor" : "Microsoft Corporation", "product" : "Microsoft Visual C++ 2008 Redistributable - x86 9.0.21022", "cpe" : None, "version" : "9.0.21022", "id" : "f7b5ca72b77093679e0832a748a38620", "cve_matches" : [ ] }] + +CPES = [{ "uri_binding" : "cpe:/a:microsoft:.net_framework:4.5", "formatted_string_binding" : "cpe:2.3:a:microsoft:.net_framework:4.5", "wfn" : { "part" : "a", "product" : ".net_framework", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "ANY", "vendor" : "microsoft", "edition" : "ANY", "version" : "4.5", "other" : "ANY", "target_hw" : "ANY" } }, + { "uri_binding" : "cpe:/a:microsoft:.net_framework:4.6.2", "formatted_string_binding" : "cpe:2.3:a:microsoft:.net_framework:4.6.2", "wfn" : { "part" : "a", "product" : ".net_framework", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "ANY", "vendor" : "microsoft", "edition" : "ANY", "version" : "4.6.2", "other" : "ANY", "target_hw" : "ANY" } }, + { "uri_binding" : "cpe:/a:microsoft:.net_framework:4.5.2", "formatted_string_binding" : "cpe:2.3:a:microsoft:.net_framework:4.5.2", "wfn" : { "part" : "a", "product" : ".net_framework", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "ANY", "vendor" : "microsoft", "edition" : "ANY", "version" : "4.5.2", "other" : "ANY", "target_hw" : "ANY" } }, + { "uri_binding" : "cpe:/a:microsoft:.net_framework:1.1:2003sp1", "formatted_string_binding" : "cpe:2.3:a:microsoft:.net_framework:1.1:2003sp1", "wfn" : { "part" : "a", "product" : ".net_framework", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "2003sp1", "vendor" : "microsoft", "edition" : "ANY", "version" : "1.1", "other" : "ANY", "target_hw" : "ANY" } }, + { "uri_binding" : "cpe:/a:microsoft:.net_framework:3.0:sp2", "formatted_string_binding" : "cpe:2.3:a:microsoft:.net_framework:3.0:sp2", "wfn" : { "part" : "a", "product" : ".net_framework", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp2", "vendor" : "microsoft", "edition" : "ANY", "version" : "3.0", "other" : "ANY", "target_hw" : "ANY" } }] + +CVES = [{ "cve_id" : "CVE-2010-3228", "cve_summary": "The JIT compiler in Microsoft .NET Framework 4.0 on 64-bit platforms does not properly perform optimizations, which allows remote attackers to execute arbitrary code via a crafted .NET application that triggers memory corruption, aka \".NET Framework x64 JIT Compiler Vulnerability.\"", "cpe_entries" : [ { "uri_binding" : "cpe:/a:microsoft:.net_framework:4.0", "wfn" : { "part" : "a", "product" : ".net_framework", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "ANY", "vendor" : "microsoft", "edition" : "ANY", "version" : "4.0", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_xp:-:sp2:x64", "wfn" : { "part" : "o", "product" : "windows_xp", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp2", "vendor" : "microsoft", "edition" : "x64", "version" : "NA", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_server_2003::sp2:x64", "wfn" : { "part" : "o", "product" : "windows_server_2003", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp2", "vendor" : "microsoft", "edition" : "x64", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_server_2003::sp2:itanium", "wfn" : { "part" : "o", "product" : "windows_server_2003", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp2", "vendor" : "microsoft", "edition" : "itanium", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_vista::sp1:x64", "wfn" : { "part" : "o", "product" : "windows_vista", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp1", "vendor" : "microsoft", "edition" : "x64", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_vista::sp2:x64", "wfn" : { "part" : "o", "product" : "windows_vista", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp2", "vendor" : "microsoft", "edition" : "x64", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_server_2008:::x64", "wfn" : { "part" : "o", "product" : "windows_server_2008", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "ANY", "vendor" : "microsoft", "edition" : "x64", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_server_2008::sp2:x64", "wfn" : { "part" : "o", "product" : "windows_server_2008", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp2", "vendor" : "microsoft", "edition" : "x64", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_server_2008:::itanium", "wfn" : { "part" : "o", "product" : "windows_server_2008", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "ANY", "vendor" : "microsoft", "edition" : "itanium", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_server_2008:-:sp2:itanium", "wfn" : { "part" : "o", "product" : "windows_server_2008", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp2", "vendor" : "microsoft", "edition" : "itanium", "version" : "NA", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_7:-:-:x64", "wfn" : { "part" : "o", "product" : "windows_7", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "NA", "vendor" : "microsoft", "edition" : "x64", "version" : "NA", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_server_2008:r2::x64", "wfn" : { "part" : "o", "product" : "windows_server_2008", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "ANY", "vendor" : "microsoft", "edition" : "x64", "version" : "r2", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_server_2008:r2::itanium", "wfn" : { "part" : "o", "product" : "windows_server_2008", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "ANY", "vendor" : "microsoft", "edition" : "itanium", "version" : "r2", "other" : "ANY", "target_hw" : "ANY" } } ] }, + {"cve_id" : "CVE-2010-3332", "cve_summary": "Microsoft .NET Framework 1.1 SP1, 2.0 SP1 and SP2, 3.5, 3.5 SP1, 3.5.1, and 4.0, as used for ASP.NET in Microsoft Internet Information Services (IIS), provides detailed error codes during decryption attempts, which allows remote attackers to decrypt and modify encrypted View State (aka __VIEWSTATE) form data, and possibly forge cookies or read application files, via a padding oracle attack, aka \"ASP.NET Padding Oracle Vulnerability.\"", "cpe_entries" : [ { "uri_binding" : "cpe:/a:microsoft:.net_framework:1.0:sp3", "wfn" : { "part" : "a", "product" : ".net_framework", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp3", "vendor" : "microsoft", "edition" : "ANY", "version" : "1.0", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/a:microsoft:.net_framework:1.1:sp1", "wfn" : { "part" : "a", "product" : ".net_framework", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp1", "vendor" : "microsoft", "edition" : "ANY", "version" : "1.1", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/a:microsoft:.net_framework:2.0:sp2", "wfn" : { "part" : "a", "product" : ".net_framework", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp2", "vendor" : "microsoft", "edition" : "ANY", "version" : "2.0", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/a:microsoft:.net_framework:3.5", "wfn" : { "part" : "a", "product" : ".net_framework", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "ANY", "vendor" : "microsoft", "edition" : "ANY", "version" : "3.5", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/a:microsoft:.net_framework:3.5.1", "wfn" : { "part" : "a", "product" : ".net_framework", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "ANY", "vendor" : "microsoft", "edition" : "ANY", "version" : "3.5.1", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/a:microsoft:.net_framework:4.0", "wfn" : { "part" : "a", "product" : ".net_framework", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "ANY", "vendor" : "microsoft", "edition" : "ANY", "version" : "4.0", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/a:microsoft:.net_framework:3.5:sp1", "wfn" : { "part" : "a", "product" : ".net_framework", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp1", "vendor" : "microsoft", "edition" : "ANY", "version" : "3.5", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/a:microsoft:.net_framework:2.0:sp1", "wfn" : { "part" : "a", "product" : ".net_framework", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp1", "vendor" : "microsoft", "edition" : "ANY", "version" : "2.0", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/a:microsoft:iis", "wfn" : { "part" : "a", "product" : "iis", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "ANY", "vendor" : "microsoft", "edition" : "ANY", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } } ] }, + {"cve_id" : "CVE-2011-1271", "cve_summary": "The JIT compiler in Microsoft .NET Framework 3.5 Gold and SP1, 3.5.1, and 4.0, when IsJITOptimizerDisabled is false, does not properly handle expressions related to null strings, which allows context-dependent attackers to bypass intended access restrictions, and consequently execute arbitrary code, in opportunistic circumstances by leveraging a crafted application, as demonstrated by (1) a crafted XAML browser application (aka XBAP), (2) a crafted ASP.NET application, or (3) a crafted .NET Framework application, aka \".NET Framework JIT Optimization Vulnerability.\"", "cpe_entries" : [ { "uri_binding" : "cpe:/a:microsoft:.net_framework:4.0", "wfn" : { "part" : "a", "product" : ".net_framework", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "ANY", "vendor" : "microsoft", "edition" : "ANY", "version" : "4.0", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_xp::sp3", "wfn" : { "part" : "o", "product" : "windows_xp", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp3", "vendor" : "microsoft", "edition" : "ANY", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_xp:-:sp2:x64", "wfn" : { "part" : "o", "product" : "windows_xp", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp2", "vendor" : "microsoft", "edition" : "x64", "version" : "NA", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_2003_server::sp2", "wfn" : { "part" : "o", "product" : "windows_2003_server", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp2", "vendor" : "microsoft", "edition" : "ANY", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_server_2003::sp2:x64", "wfn" : { "part" : "o", "product" : "windows_server_2003", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp2", "vendor" : "microsoft", "edition" : "x64", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_server_2003::sp2:itanium", "wfn" : { "part" : "o", "product" : "windows_server_2003", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp2", "vendor" : "microsoft", "edition" : "itanium", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_vista::sp1", "wfn" : { "part" : "o", "product" : "windows_vista", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp1", "vendor" : "microsoft", "edition" : "ANY", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_vista::sp2", "wfn" : { "part" : "o", "product" : "windows_vista", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp2", "vendor" : "microsoft", "edition" : "ANY", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_vista::sp1:x64", "wfn" : { "part" : "o", "product" : "windows_vista", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp1", "vendor" : "microsoft", "edition" : "x64", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_vista::sp2:x64", "wfn" : { "part" : "o", "product" : "windows_vista", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp2", "vendor" : "microsoft", "edition" : "x64", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_server_2008:::x32", "wfn" : { "part" : "o", "product" : "windows_server_2008", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "ANY", "vendor" : "microsoft", "edition" : "x32", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_server_2008::sp2:x32", "wfn" : { "part" : "o", "product" : "windows_server_2008", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp2", "vendor" : "microsoft", "edition" : "x32", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_server_2008:::x64", "wfn" : { "part" : "o", "product" : "windows_server_2008", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "ANY", "vendor" : "microsoft", "edition" : "x64", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_server_2008::sp2:x64", "wfn" : { "part" : "o", "product" : "windows_server_2008", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp2", "vendor" : "microsoft", "edition" : "x64", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_server_2008:::itanium", "wfn" : { "part" : "o", "product" : "windows_server_2008", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "ANY", "vendor" : "microsoft", "edition" : "itanium", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_server_2008:-:sp2:itanium", "wfn" : { "part" : "o", "product" : "windows_server_2008", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp2", "vendor" : "microsoft", "edition" : "itanium", "version" : "NA", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_7:-:-:x32", "wfn" : { "part" : "o", "product" : "windows_7", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "NA", "vendor" : "microsoft", "edition" : "x32", "version" : "NA", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_7:-:sp1:x32", "wfn" : { "part" : "o", "product" : "windows_7", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp1", "vendor" : "microsoft", "edition" : "x32", "version" : "NA", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_7:-:-:x64", "wfn" : { "part" : "o", "product" : "windows_7", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "NA", "vendor" : "microsoft", "edition" : "x64", "version" : "NA", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_server_2008:r2::x64", "wfn" : { "part" : "o", "product" : "windows_server_2008", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "ANY", "vendor" : "microsoft", "edition" : "x64", "version" : "r2", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_server_2008:r2:sp1:x64", "wfn" : { "part" : "o", "product" : "windows_server_2008", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp1", "vendor" : "microsoft", "edition" : "x64", "version" : "r2", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_server_2008:r2::itanium", "wfn" : { "part" : "o", "product" : "windows_server_2008", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "ANY", "vendor" : "microsoft", "edition" : "itanium", "version" : "r2", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_server_2008:r2:sp1:itanium", "wfn" : { "part" : "o", "product" : "windows_server_2008", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp1", "vendor" : "microsoft", "edition" : "itanium", "version" : "r2", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/a:microsoft:.net_framework:3.5.1", "wfn" : { "part" : "a", "product" : ".net_framework", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "ANY", "vendor" : "microsoft", "edition" : "ANY", "version" : "3.5.1", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_7:-:-:x32", "wfn" : { "part" : "o", "product" : "windows_7", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "NA", "vendor" : "microsoft", "edition" : "x32", "version" : "NA", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_7:-:-:x64", "wfn" : { "part" : "o", "product" : "windows_7", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "NA", "vendor" : "microsoft", "edition" : "x64", "version" : "NA", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_server_2008:r2::x64", "wfn" : { "part" : "o", "product" : "windows_server_2008", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "ANY", "vendor" : "microsoft", "edition" : "x64", "version" : "r2", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_server_2008:r2::itanium", "wfn" : { "part" : "o", "product" : "windows_server_2008", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "ANY", "vendor" : "microsoft", "edition" : "itanium", "version" : "r2", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_7:-:sp1:x32", "wfn" : { "part" : "o", "product" : "windows_7", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp1", "vendor" : "microsoft", "edition" : "x32", "version" : "NA", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_7:-:sp1:x64", "wfn" : { "part" : "o", "product" : "windows_7", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp1", "vendor" : "microsoft", "edition" : "x64", "version" : "NA", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_server_2008:r2:sp1:x64", "wfn" : { "part" : "o", "product" : "windows_server_2008", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp1", "vendor" : "microsoft", "edition" : "x64", "version" : "r2", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_server_2008:r2:sp1:itanium", "wfn" : { "part" : "o", "product" : "windows_server_2008", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp1", "vendor" : "microsoft", "edition" : "itanium", "version" : "r2", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/a:microsoft:.net_framework:2.0:sp2", "wfn" : { "part" : "a", "product" : ".net_framework", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp2", "vendor" : "microsoft", "edition" : "ANY", "version" : "2.0", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_xp::sp3", "wfn" : { "part" : "o", "product" : "windows_xp", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp3", "vendor" : "microsoft", "edition" : "ANY", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_xp:-:sp2:x64", "wfn" : { "part" : "o", "product" : "windows_xp", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp2", "vendor" : "microsoft", "edition" : "x64", "version" : "NA", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_server_2003::sp2:x64", "wfn" : { "part" : "o", "product" : "windows_server_2003", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp2", "vendor" : "microsoft", "edition" : "x64", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_server_2003::sp2:itanium", "wfn" : { "part" : "o", "product" : "windows_server_2003", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp2", "vendor" : "microsoft", "edition" : "itanium", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_vista::sp2", "wfn" : { "part" : "o", "product" : "windows_vista", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp2", "vendor" : "microsoft", "edition" : "ANY", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_vista::sp1", "wfn" : { "part" : "o", "product" : "windows_vista", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp1", "vendor" : "microsoft", "edition" : "ANY", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_vista::sp1:x64", "wfn" : { "part" : "o", "product" : "windows_vista", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp1", "vendor" : "microsoft", "edition" : "x64", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_vista::sp2:x64", "wfn" : { "part" : "o", "product" : "windows_vista", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp2", "vendor" : "microsoft", "edition" : "x64", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_server_2008:::x32", "wfn" : { "part" : "o", "product" : "windows_server_2008", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "ANY", "vendor" : "microsoft", "edition" : "x32", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_server_2008::sp2:x32", "wfn" : { "part" : "o", "product" : "windows_server_2008", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp2", "vendor" : "microsoft", "edition" : "x32", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_server_2008:::x64", "wfn" : { "part" : "o", "product" : "windows_server_2008", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "ANY", "vendor" : "microsoft", "edition" : "x64", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_server_2008::sp2:x64", "wfn" : { "part" : "o", "product" : "windows_server_2008", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp2", "vendor" : "microsoft", "edition" : "x64", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_server_2008:::itanium", "wfn" : { "part" : "o", "product" : "windows_server_2008", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "ANY", "vendor" : "microsoft", "edition" : "itanium", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_server_2008:-:sp2:itanium", "wfn" : { "part" : "o", "product" : "windows_server_2008", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp2", "vendor" : "microsoft", "edition" : "itanium", "version" : "NA", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_2003_server::sp2", "wfn" : { "part" : "o", "product" : "windows_2003_server", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp2", "vendor" : "microsoft", "edition" : "ANY", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/a:microsoft:.net_framework:3.5:sp1", "wfn" : { "part" : "a", "product" : ".net_framework", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp1", "vendor" : "microsoft", "edition" : "ANY", "version" : "3.5", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_xp::sp3", "wfn" : { "part" : "o", "product" : "windows_xp", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp3", "vendor" : "microsoft", "edition" : "ANY", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_xp:-:sp2:x64", "wfn" : { "part" : "o", "product" : "windows_xp", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp2", "vendor" : "microsoft", "edition" : "x64", "version" : "NA", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_2003_server::sp2", "wfn" : { "part" : "o", "product" : "windows_2003_server", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp2", "vendor" : "microsoft", "edition" : "ANY", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_server_2003::sp2:x64", "wfn" : { "part" : "o", "product" : "windows_server_2003", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp2", "vendor" : "microsoft", "edition" : "x64", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_server_2003::sp2:itanium", "wfn" : { "part" : "o", "product" : "windows_server_2003", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp2", "vendor" : "microsoft", "edition" : "itanium", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_vista::sp1", "wfn" : { "part" : "o", "product" : "windows_vista", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp1", "vendor" : "microsoft", "edition" : "ANY", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_vista::sp2", "wfn" : { "part" : "o", "product" : "windows_vista", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp2", "vendor" : "microsoft", "edition" : "ANY", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_vista::sp1:x64", "wfn" : { "part" : "o", "product" : "windows_vista", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp1", "vendor" : "microsoft", "edition" : "x64", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_vista::sp2:x64", "wfn" : { "part" : "o", "product" : "windows_vista", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp2", "vendor" : "microsoft", "edition" : "x64", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_server_2008::sp2:x32", "wfn" : { "part" : "o", "product" : "windows_server_2008", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp2", "vendor" : "microsoft", "edition" : "x32", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_server_2008:::x32", "wfn" : { "part" : "o", "product" : "windows_server_2008", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "ANY", "vendor" : "microsoft", "edition" : "x32", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_server_2008:::x64", "wfn" : { "part" : "o", "product" : "windows_server_2008", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "ANY", "vendor" : "microsoft", "edition" : "x64", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_server_2008::sp2:x64", "wfn" : { "part" : "o", "product" : "windows_server_2008", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp2", "vendor" : "microsoft", "edition" : "x64", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_server_2008:-:sp2:itanium", "wfn" : { "part" : "o", "product" : "windows_server_2008", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp2", "vendor" : "microsoft", "edition" : "itanium", "version" : "NA", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_server_2008:::itanium", "wfn" : { "part" : "o", "product" : "windows_server_2008", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "ANY", "vendor" : "microsoft", "edition" : "itanium", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/a:microsoft:.net_framework:2.0:sp1", "wfn" : { "part" : "a", "product" : ".net_framework", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp1", "vendor" : "microsoft", "edition" : "ANY", "version" : "2.0", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_vista::sp1", "wfn" : { "part" : "o", "product" : "windows_vista", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp1", "vendor" : "microsoft", "edition" : "ANY", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_vista::sp1:x64", "wfn" : { "part" : "o", "product" : "windows_vista", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp1", "vendor" : "microsoft", "edition" : "x64", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_server_2008:::x32", "wfn" : { "part" : "o", "product" : "windows_server_2008", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "ANY", "vendor" : "microsoft", "edition" : "x32", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_server_2008:::x64", "wfn" : { "part" : "o", "product" : "windows_server_2008", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "ANY", "vendor" : "microsoft", "edition" : "x64", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_server_2008:::itanium", "wfn" : { "part" : "o", "product" : "windows_server_2008", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "ANY", "vendor" : "microsoft", "edition" : "itanium", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/a:microsoft:.net_framework:3.5", "wfn" : { "part" : "a", "product" : ".net_framework", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "ANY", "vendor" : "microsoft", "edition" : "ANY", "version" : "3.5", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_xp::sp3", "wfn" : { "part" : "o", "product" : "windows_xp", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp3", "vendor" : "microsoft", "edition" : "ANY", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_xp:-:sp2:x64", "wfn" : { "part" : "o", "product" : "windows_xp", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp2", "vendor" : "microsoft", "edition" : "x64", "version" : "NA", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_2003_server::sp2", "wfn" : { "part" : "o", "product" : "windows_2003_server", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp2", "vendor" : "microsoft", "edition" : "ANY", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_server_2003::sp2:x64", "wfn" : { "part" : "o", "product" : "windows_server_2003", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp2", "vendor" : "microsoft", "edition" : "x64", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_server_2003::sp2:itanium", "wfn" : { "part" : "o", "product" : "windows_server_2003", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp2", "vendor" : "microsoft", "edition" : "itanium", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_vista::sp2", "wfn" : { "part" : "o", "product" : "windows_vista", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp2", "vendor" : "microsoft", "edition" : "ANY", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_vista::sp1", "wfn" : { "part" : "o", "product" : "windows_vista", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp1", "vendor" : "microsoft", "edition" : "ANY", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_vista::sp1:x64", "wfn" : { "part" : "o", "product" : "windows_vista", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp1", "vendor" : "microsoft", "edition" : "x64", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_vista::sp2:x64", "wfn" : { "part" : "o", "product" : "windows_vista", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp2", "vendor" : "microsoft", "edition" : "x64", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_server_2008:::x32", "wfn" : { "part" : "o", "product" : "windows_server_2008", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "ANY", "vendor" : "microsoft", "edition" : "x32", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_server_2008:::x64", "wfn" : { "part" : "o", "product" : "windows_server_2008", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "ANY", "vendor" : "microsoft", "edition" : "x64", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/o:microsoft:windows_server_2008:::itanium", "wfn" : { "part" : "o", "product" : "windows_server_2008", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "ANY", "vendor" : "microsoft", "edition" : "itanium", "version" : "ANY", "other" : "ANY", "target_hw" : "ANY" } } ] }, + {"cve_id" : "CVE-2012-0160", "cve_summary": "Microsoft .NET Framework 1.0 SP3, 1.1 SP1, 2.0 SP2, 3.0 SP2, 3.5 SP1, 3.5.1, and 4 does not properly serialize input data, which allows remote attackers to execute arbitrary code via (1) a crafted XAML browser application (aka XBAP) or (2) a crafted .NET Framework application, aka \".NET Framework Serialization Vulnerability.\"", "cpe_entries" : [ { "uri_binding" : "cpe:/a:microsoft:.net_framework:1.0:sp3", "wfn" : { "part" : "a", "product" : ".net_framework", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp3", "vendor" : "microsoft", "edition" : "ANY", "version" : "1.0", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/a:microsoft:.net_framework:1.1:sp1", "wfn" : { "part" : "a", "product" : ".net_framework", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp1", "vendor" : "microsoft", "edition" : "ANY", "version" : "1.1", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/a:microsoft:.net_framework:2.0:sp2", "wfn" : { "part" : "a", "product" : ".net_framework", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp2", "vendor" : "microsoft", "edition" : "ANY", "version" : "2.0", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/a:microsoft:.net_framework:3.0:sp2", "wfn" : { "part" : "a", "product" : ".net_framework", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp2", "vendor" : "microsoft", "edition" : "ANY", "version" : "3.0", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/a:microsoft:.net_framework:3.5:sp1", "wfn" : { "part" : "a", "product" : ".net_framework", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp1", "vendor" : "microsoft", "edition" : "ANY", "version" : "3.5", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/a:microsoft:.net_framework:3.5.1", "wfn" : { "part" : "a", "product" : ".net_framework", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "ANY", "vendor" : "microsoft", "edition" : "ANY", "version" : "3.5.1", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/a:microsoft:.net_framework:4.0", "wfn" : { "part" : "a", "product" : ".net_framework", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "ANY", "vendor" : "microsoft", "edition" : "ANY", "version" : "4.0", "other" : "ANY", "target_hw" : "ANY" } } ] }, + {"cve_id" : "CVE-2012-0164", "cve_summary": "Microsoft .NET Framework 4 does not properly compare index values, which allows remote attackers to cause a denial of service (application hang) via crafted requests to a Windows Presentation Foundation (WPF) application, aka \".NET Framework Index Comparison Vulnerability.\"", "cpe_entries" : [ { "uri_binding" : "cpe:/a:microsoft:.net_framework:4.0", "wfn" : { "part" : "a", "product" : ".net_framework", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "ANY", "vendor" : "microsoft", "edition" : "ANY", "version" : "4.0", "other" : "ANY", "target_hw" : "ANY" } } ] }, + {"cve_id" : "CVE-2008-2948", "cve_summary": "Cross-domain vulnerability in Microsoft Internet Explorer 7 and 8 allows remote attackers to change the location property of a frame via the Object data type, and use a frame from a different domain to observe domain-independent events, as demonstrated by observing onkeydown events with caballero-listener. NOTE: according to Microsoft, this is a duplicate of CVE-2008-2947, possibly a different attack vector.", "cpe_entries" : [ { "uri_binding" : "cpe:/a:microsoft:ie:7", "wfn" : { "part" : "a", "product" : "ie", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "ANY", "vendor" : "microsoft", "edition" : "ANY", "version" : "7", "other" : "ANY", "target_hw" : "ANY" } }, { "uri_binding" : "cpe:/a:microsoft:internet_explorer:8", "wfn" : { "part" : "a", "product" : "internet_explorer", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "ANY", "vendor" : "microsoft", "edition" : "ANY", "version" : "8", "other" : "ANY", "target_hw" : "ANY" } } ] }, + {"cve_id" : "CVE-2008-5551", "cve_summary": "The XSS Filter in Microsoft Internet Explorer 8.0 Beta 2 allows remote attackers to bypass the XSS protection mechanism and conduct XSS attacks by injecting data at two different positions within an HTML document, related to STYLE elements and the CSS expression property, aka a \"double injection.\"", "cpe_entries" : [ { "uri_binding" : "cpe:/a:microsoft:internet_explorer:8:beta2", "wfn" : { "part" : "a", "product" : "internet_explorer", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "beta2", "vendor" : "microsoft", "edition" : "ANY", "version" : "8", "other" : "ANY", "target_hw" : "ANY" } } ] }, + {"cve_id" : "CVE-2008-5552", "cve_summary": "The XSS Filter in Microsoft Internet Explorer 8.0 Beta 2 allows remote attackers to bypass the XSS protection mechanism and conduct XSS attacks via a CRLF sequence in conjunction with a crafted Content-Type header, as demonstrated by a header with a utf-7 charset value. NOTE: the vendor has reportedly stated that the XSS Filter intentionally does not attempt to \"address every conceivable XSS attack scenario.\"", "cpe_entries" : [ { "uri_binding" : "cpe:/a:microsoft:internet_explorer:8:beta2", "wfn" : { "part" : "a", "product" : "internet_explorer", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "beta2", "vendor" : "microsoft", "edition" : "ANY", "version" : "8", "other" : "ANY", "target_hw" : "ANY" } } ] }, + {"cve_id" : "CVE-2008-5556", "cve_summary": "** DISPUTED ** The XSS Filter in Microsoft Internet Explorer 8.0 Beta 2 does not recognize attack patterns designed to operate against web pages that are encoded with utf-7, which allows remote attackers to bypass the XSS protection mechanism and conduct XSS attacks by injecting crafted utf-7 content. NOTE: the vendor reportedly disputes this issue, stating \"Behaviour is by design.\"", "cpe_entries" : [ { "uri_binding" : "cpe:/a:microsoft:internet_explorer:8:beta2", "wfn" : { "part" : "a", "product" : "internet_explorer", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "beta2", "vendor" : "microsoft", "edition" : "ANY", "version" : "8", "other" : "ANY", "target_hw" : "ANY" } } ] }] + +COLLECTIONS = namedtuple('COLLECTIONS', ['inventory', 'cpes', 'cves'])('inventory', 'cpes', 'cves') + + +def create_db(): + insert_docs_in_collection(INVENTORY, COLLECTIONS.inventory) + insert_docs_in_collection(CPES, COLLECTIONS.cpes) + insert_docs_in_collection(CVES, COLLECTIONS.cves) + + +def insert_docs_in_collection(docs, collection): + db = Database() + if is_collection_empty(db, collection): + for doc in docs: + db.insert_document_in_collection(doc, collection) + + +def is_collection_empty(db, collection): + return db.get_number_of_documents_in_collection(collection) == 0 + diff --git a/frontend/frontend_env/__init__.py b/frontend/frontend_env/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/frontend/frontend_env/settings.py b/frontend/frontend_env/settings.py new file mode 100644 index 0000000..825a618 --- /dev/null +++ b/frontend/frontend_env/settings.py @@ -0,0 +1,132 @@ +""" +Django settings for frontend_env project. + +Generated by 'django-admin startproject' using Django 1.9.7. + +For more information on this file, see +https://docs.djangoproject.com/en/1.9/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/1.9/ref/settings/ +""" + +import os + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'b$-owb3rr!3^@$_km=wmdz%26t*23v991%p($!xgfnoz6^p@q@' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + 'iva.apps.FrontendConfig', + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', +] + +MIDDLEWARE_CLASSES = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'frontend_env.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'frontend_env.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/1.9/ref/settings/#databases + +# DATABASES = { +# 'default': { +# 'ENGINE': 'django.db.backends.sqlite3', +# 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), +# } +# } + + +# Password validation +# https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/1.9/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/1.9/howto/static-files/ + +STATIC_URL = '/static/' + +SESSION_ENGINE = "django.contrib.sessions.backends.cache" + +CACHES = { + 'default': { + 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', + 'LOCATION': 'unique-snowflake', + } +} + diff --git a/frontend/frontend_env/urls.py b/frontend/frontend_env/urls.py new file mode 100644 index 0000000..57a086f --- /dev/null +++ b/frontend/frontend_env/urls.py @@ -0,0 +1,22 @@ +"""frontend_env URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/1.9/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.conf.urls import url, include + 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) +""" +from django.conf.urls import include, url +from django.contrib import admin + +urlpatterns = [ + url(r'^admin/', admin.site.urls), + url(r'^iva/', include('iva.urls')), +] \ No newline at end of file diff --git a/frontend/frontend_env/wsgi.py b/frontend/frontend_env/wsgi.py new file mode 100644 index 0000000..a9e2efd --- /dev/null +++ b/frontend/frontend_env/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for frontend_env project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/1.9/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "frontend_env.settings") + +application = get_wsgi_application() diff --git a/frontend/iva/__init__.py b/frontend/iva/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/frontend/iva/admin.py b/frontend/iva/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/frontend/iva/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/frontend/iva/apps.py b/frontend/iva/apps.py new file mode 100644 index 0000000..d2d9cd8 --- /dev/null +++ b/frontend/iva/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class FrontendConfig(AppConfig): + name = 'iva' diff --git a/frontend/iva/handlers/add_user_handler.py b/frontend/iva/handlers/add_user_handler.py new file mode 100644 index 0000000..c4528a9 --- /dev/null +++ b/frontend/iva/handlers/add_user_handler.py @@ -0,0 +1,50 @@ +from django.http import HttpResponse +from django.template import loader + +from user_authentication.user import User +from .request_handler_utils import * + +TEMPLATE = loader.get_template('iva/add_user.html') +TEMPLATE_USERS = loader.get_template('iva/users.html') + + +def handle_request(request): + handler = get_handler(request) + http_response = handler.handle_request() + return http_response + + +def get_handler(request): + if is_get_request(request): + return GetHandler(request) + return PostHandler(request) + + +class GetHandler: + + def __init__(self, request): + self.request = request + self.context = {} + self.user = User() + + def handle_request(self): + if self.request.GET.get('option') == 'check_user': + if self.user.exist_user_with_username(self.request.GET.get('username')): + return HttpResponse({'exist'}, self.request) + return HttpResponse({'not_exist': False}, self.request) + return HttpResponse(TEMPLATE.render({}, self.request)) + + +class PostHandler: + + def __init__(self, request): + self.request = request + self.name = self.request.POST.get('name') + self.surname = self.request.POST.get('surname') + self.username = self.request.POST.get('username') + self.pwd = self.request.POST.get('password') + self.user = User() + + def handle_request(self): + self.user.insert_new_user(self.name, self.surname, self.username, self.pwd) + return HttpResponse(TEMPLATE_USERS.render({'users': self.user.get_users()}, self.request)) diff --git a/frontend/iva/handlers/alert_log_handler.py b/frontend/iva/handlers/alert_log_handler.py new file mode 100644 index 0000000..9ffd250 --- /dev/null +++ b/frontend/iva/handlers/alert_log_handler.py @@ -0,0 +1,22 @@ +from django.http import HttpResponse +from django.template import loader +from .request_handler_utils import * + +template = loader.get_template('iva/alert_log.html') + + +def handle_request(request): + if is_get_request(request): + get_handler = GetHandler(request) + return get_handler.create_http_response() + + +class GetHandler: + def __init__(self, request): + self.request = request + + def create_http_response(self): + return HttpResponse(template.render(self.create_context(), self.request)) + + def create_context(self): + return {'log': ast.literal_eval(self.request.GET.get('log'))} diff --git a/frontend/iva/handlers/alert_notes_handler.py b/frontend/iva/handlers/alert_notes_handler.py new file mode 100644 index 0000000..c269370 --- /dev/null +++ b/frontend/iva/handlers/alert_notes_handler.py @@ -0,0 +1,47 @@ +from django.http import HttpResponse +from django.template import loader + +from alerts.alerts import Alerts +from .request_handler_utils import * + +template = loader.get_template('iva/alert_notes.html') + + +def handle_request(request): + if is_get_request(request): + get_handler = GetHandler(request) + return get_handler.create_http_response() + elif is_post_request(request): + post_request = PostHandler(request) + post_request.handle_post_request() + return post_request.create_http_response() + + +class GetHandler: + def __init__(self, request): + self.request = request + self.software_id = self.request.GET.get('software_id') + self.notes = self.request.GET.get('notes') + + def create_http_response(self): + return HttpResponse(template.render(self.create_context(), self.request)) + + def create_context(self): + return {'notes': self.notes, 'software_id': self.software_id} + + +class PostHandler: + + def __init__(self, request): + self.alerts = Alerts() + self.request = request + self.item_id = self.request.POST.get('software_id') + self.notes = self.request.POST.get('notes') + + def handle_post_request(self): + self.alerts.update_notes(self.item_id, self.notes) + + @staticmethod + def create_http_response(): + return HttpResponse('') \ No newline at end of file diff --git a/frontend/iva/handlers/alert_status_handler.py b/frontend/iva/handlers/alert_status_handler.py new file mode 100644 index 0000000..d2b97f7 --- /dev/null +++ b/frontend/iva/handlers/alert_status_handler.py @@ -0,0 +1,62 @@ +from django.http import HttpResponse +from django.template import loader +from alerts.alerts import Alerts +from inventory.software_cve_matches import CVEMatches +from .request_handler_utils import * +from alerts.alerts import STATUS + +template = loader.get_template('iva/alert_status.html') + + +def handle_request(request): + if is_get_request(request): + get_handler = GetHandler(request) + return get_handler.create_http_response() + elif is_post_request(request): + post_request = PostHandler(request) + post_request.handle_post_request() + return post_request.create_http_response() + + +class GetHandler: + + def __init__(self, request): + self.request = request + self.current_status = self.request.GET.get('current_status') + self.software_id = self.request.GET.get('software_id') + + def create_http_response(self): + return HttpResponse(template.render(self.create_context(), self.request)) + + def create_context(self): + return {'software_id': self.software_id, 'current_status': self.current_status, 'status_list': self.get_status_list()} + + def get_status_list(self): + status_list = list(STATUS) + status_list.remove(self.current_status) + return status_list + + +class PostHandler: + + def __init__(self, request): + self.alerts = Alerts() + self.cve_matches = CVEMatches() + self.request = request + self.software_id = self.request.POST.get('software_id') + self.new_status = self.request.POST.get('new_status') + + def handle_post_request(self): + self.set_cves_as_negatives() + self.alerts.change_alert_status(self.software_id, self.new_status) + + def set_cves_as_negatives(self): + if self.new_status == 'removed': + alert = self.alerts.get_software_alert(self.software_id) + for cve in alert.get('cves'): + self.cve_matches.set_cve_match_as_negative(self.software_id, cve) + + @staticmethod + def create_http_response(): + return HttpResponse('') diff --git a/frontend/iva/handlers/alerts_handler.py b/frontend/iva/handlers/alerts_handler.py new file mode 100644 index 0000000..a6cd1b1 --- /dev/null +++ b/frontend/iva/handlers/alerts_handler.py @@ -0,0 +1,84 @@ +import config +from django.http import HttpResponse +from django.template import loader +from alerts.alerts import Alerts +from inventory.software_cve_matches import CVEMatches +from inventory.inventory import Inventory +from .request_handler_utils import * + +template = loader.get_template('iva/alerts.html') + + +def handle_request(request): + if is_get_request(request): + get_handler = GetHandler(request) + get_handler.handle_get_request() + return get_handler.create_http_response() + + elif is_post_request(request): + post_handler = PostHandler(request) + return post_handler.handle_post_request() + + +class PostHandler: + + def __init__(self, request): + self.request = request + self.option = self.post_value('option') + self.software_id = self.post_value('software_id') + self.cve_id = self.post_value('cve_id') + self.cve_matches = CVEMatches() + self.alerts = Alerts() + + def handle_post_request(self): + if self.option == 'set_cve_as_negative': + self.cve_matches.set_cve_match_as_negative(self.software_id, self.cve_id) + elif self.option == 'notify': + was_sent = self.alerts.send_sw_alert_by_email(self.software_id) + if not was_sent: + return HttpResponse('failed') + return HttpResponse('ok') + + def post_value(self, key): + return self.request.POST.get(key) + + +class GetHandler: + + def __init__(self, request): + self.alerts = Alerts() + self.inventory = Inventory() + self.request = request + + def create_http_response(self): + return HttpResponse(template.render(self.create_context(), self.request)) + + def handle_get_request(self): + pass + + def create_context(self): + return {'alerts': self.get_alerts(), 'cve_search_url': config.get_cve_search_url()} + + def get_alerts(self): + alerts = self.alerts.get_alerts() + for alert in alerts: + self.add_cpe_to_alert(alert) + format_alert_log(alert) + return alerts + + def add_cpe_to_alert(self, alert): + sw = self.inventory.get_software_by_id(alert.get('software_id')) + if sw is not None: + alert.update({'uri': sw.get('cpe').get('uri_binding')}) + alert.update({'product': sw.get('product')}) + alert.update({'vendor': sw.get('vendor')}) + alert.update({'version': sw.get('version')}) + else: + alert.update({'product': 'product was removed from database'}) + + +def format_alert_log(alert): + log = [] + for entry in alert.get('log'): + log.append(entry.get('date').strftime("%d.%m.%Y %H:%M") + ' ' + entry.get('event')) + alert.update({'log': log}) \ No newline at end of file diff --git a/frontend/iva/handlers/assign_cpe_handler.py b/frontend/iva/handlers/assign_cpe_handler.py new file mode 100644 index 0000000..7615b7b --- /dev/null +++ b/frontend/iva/handlers/assign_cpe_handler.py @@ -0,0 +1,127 @@ +from copy import copy + +import pycountry +from django.http import HttpResponse +from django.template import loader +from inventory.software_cpe import SoftwareCPE +from matching.cpe_matcher import CPEMatcher +from inventory.inventory import Inventory +from wfn.wfn_converter import WFNConverter +from .request_handler_utils import * +from django.http import JsonResponse +from wfn.encoding import Decoder +from matching.software_formatter import FormattedSoftware + +template = loader.get_template('iva/assign_cpe.html') +software_cpe_template = loader.get_template('iva/sw_products_with_cpe.html') + + +def handle_request(request): + if is_get_request(request): + get_handler = GetHandler(request) + http_response = get_handler.handle_request() + return http_response + elif is_post_request(request): + post_handler = PostHandler(request) + return post_handler.handle_request() + + +class GetHandler: + + def __init__(self, request): + self.request = request + self.software = self.get_software() + self.cpe_candidates = self.get_cpe_matches_for_software() + + def get_software(self): + return Inventory().get_software_by_id(self.request.GET.get('id')) + + def get_cpe_matches_for_software(self): + return CPEMatcher().search_cpes_for_software(self.software) + + def handle_request(self): + return HttpResponse(template.render(self.create_context(), self.request)) + + def create_context(self): + return {'software': self.software, + 'software_id': self.software.get('id'), + 'cpe_matches': self.cpe_candidates, + 'wfn': self.get_wfn_of_best_cpe_candidate(), + 'lang_codes': pycountry.languages} + + def get_wfn_of_best_cpe_candidate(self): + if len(self.cpe_candidates) > 0: + return self.get_wfn_of_best_cpe_candidate_from_cpe_candidates() + return self.create_best_cpe_candidate_from_software_info() + + def get_wfn_of_best_cpe_candidate_from_cpe_candidates(self): + best_candidate = copy(self.cpe_candidates[0]) + self.update_wfn_version_of_best_cpe_candidate(best_candidate) + return best_candidate.get('wfn') + + def update_wfn_version_of_best_cpe_candidate(self, best_cpe_candidate): + sw_version = get_version_from_dict(self.software) + best_cpe_candidate_wfn = get_wfn_from_cpe(best_cpe_candidate) + best_cpe_candidate_version = get_version_from_dict(best_cpe_candidate_wfn) + if (best_cpe_candidate_version != sw_version) and (sw_version != ''): + update_wfn_version(sw_version, best_cpe_candidate_wfn) + update_wfn_in_cpe(best_cpe_candidate, best_cpe_candidate_wfn) + + def create_best_cpe_candidate_from_software_info(self): + formatted_software = FormattedSoftware(self.software) + return {'part': 'a', + 'vendor': formatted_software.vendor, + 'product': formatted_software.product, + 'version': self.software.get('version'), + 'update': 'ANY', 'edition': 'ANY', 'language': 'ANY', 'sw_edition': 'ANY', 'target_sw': 'ANY', + 'target_hw': 'ANY', 'other': 'ANY'} + + +class PostHandler: + + def __init__(self, request): + self.sw_cpe = SoftwareCPE() + self.wfn_converter = WFNConverter() + self.inventory = Inventory() + self.request = request + self.decoder = Decoder() + + def handle_request(self): + if not self.request.POST.get('check'): + sw_cpe_dict = self.sw_cpe.create_sw_cpe_dict(self.get_wfn()) + self.sw_cpe.assign_cpe_to_software(sw_cpe_dict, self.get_software_id()) + return self.create_http_response_for_add_cpe(sw_cpe_dict) + return self.create_http_response_for_verify_cpe_exists() + + def get_wfn(self): + return self.wfn_converter.create_wfn_from_user_input(self.request.POST) + + def get_software_id(self): + return self.request.POST.get('software_id') + + def create_http_response_for_verify_cpe_exists(self): + uri = self.wfn_converter.convert_wfn_to_uri(self.get_wfn()) + software = self.sw_cpe.get_software_cpe_by_uri(uri) + if software is not None: + return JsonResponse({'result': 'exist', 'uri_binding': self.decoder.decode_non_alphanumeric_characters(uri)}) + return JsonResponse({'result': 'not_exist', 'uri_binding': self.decoder.decode_non_alphanumeric_characters(uri)}) + + def create_http_response_for_add_cpe(self, cpe_doc): + return HttpResponse(software_cpe_template.render({'inventory': self.inventory.get_software_products_with_assigned_cpe(), + 'updated': cpe_doc.get('uri_binding')}, self.request)) + + +def update_wfn_in_cpe(cpe, wfn): + cpe.update({'wfn': wfn}) + + +def update_wfn_version(software_version, wfn): + wfn.update({'version': software_version}) + + +def get_version_from_dict(dict_): + return dict_.get('version') + + +def get_wfn_from_cpe(cpe): + return dict(cpe.get('wfn')) \ No newline at end of file diff --git a/frontend/iva/handlers/change_daily_db_update_time_handler.py b/frontend/iva/handlers/change_daily_db_update_time_handler.py new file mode 100644 index 0000000..1fd552c --- /dev/null +++ b/frontend/iva/handlers/change_daily_db_update_time_handler.py @@ -0,0 +1,57 @@ +from django.http import HttpResponse +from local_repositories.tasks.task_update_db import DailyUpdate +from local_repositories.tasks.scheduler import TaskScheduler +from .request_handler_utils import * +from django.template import loader + + +TEMPLATE = loader.get_template('iva/change_daily_db_update_time.html') + + +def handle_request(request): + handler = get_handler(request) + http_response = handler.handle_request() + return http_response + + +def get_handler(request): + if is_get_request(request): + return GetHandler(request) + return PostHandler(request) + + +class GetHandler: + + def __init__(self, request): + self.request = request + self.task_name = self.request.GET.get('name') + self.current_hour = self.request.GET.get('hour') + self.current_minute = self.request.GET.get('minute') + + def handle_request(self): + return HttpResponse(TEMPLATE.render({'task_name': self.task_name, + 'range_hour': range(24), + 'range_minute': range(60), + 'current_hour': self.current_hour, + 'current_minute': self.current_minute}, self.request)) + + +class PostHandler: + + def __init__(self, request): + self.request = request + self.daily_db_update = DailyUpdate() + self.hour = self.request.POST.get('execution_time_hour') + self.minute = self.request.POST.get('execution_time_minute') + self.task_name = self.request.POST.get('task_name') + self.task_scheduler = TaskScheduler() + + def handle_request(self): + self.daily_db_update.update_execution_time(self.get_time()) + self.task_scheduler.cancel_task(self.daily_db_update) + self.task_scheduler.reschedule_task(self.daily_db_update) + return HttpResponse('') + + def get_time(self): + return self.hour + ':' + self.minute + ':' + '00' diff --git a/frontend/iva/handlers/change_user_pwd_handler.py b/frontend/iva/handlers/change_user_pwd_handler.py new file mode 100644 index 0000000..9f001ef --- /dev/null +++ b/frontend/iva/handlers/change_user_pwd_handler.py @@ -0,0 +1,53 @@ +from django.http import HttpResponse +from django.template import loader + +from user_authentication.user import User +from .request_handler_utils import * + +TEMPLATE = loader.get_template('iva/change_user_pwd.html') +USERS_TEMPLATE = loader.get_template('iva/users.html') + + +def handle_request(request): + handler = get_handler(request) + http_response = handler.handle_request() + return http_response + + +def get_handler(request): + if is_get_request(request): + return GetHandler(request) + return PostHandler(request) + + +class GetHandler: + + def __init__(self, request): + self.request = request + self.username = self.request.GET.get('username') + + def handle_request(self): + return HttpResponse(TEMPLATE.render({'username': self.username}, self.request)) + + +class PostHandler: + + def __init__(self, request): + self.request = request + self.username = self.request.POST.get('username') + self.old_pwd = self.request.POST.get('old_pwd') + self.new_pwd = self.request.POST.get('new_pwd') + self.user = User() + + def handle_request(self): + if self.request.POST.get('verify_old_pwd'): + if self.user.exists_in_db(self.username, self.old_pwd): + return HttpResponse(True, self.request) + return HttpResponse(False, self.request) + else: + pwd_changed = self.user.change_password(self.username, self.old_pwd, self.new_pwd) + if pwd_changed: + return HttpResponse('') + return HttpResponse(TEMPLATE.render({'username': self.username, 'error': True}, self.request)) + diff --git a/frontend/iva/handlers/compare_cpe_handler.py b/frontend/iva/handlers/compare_cpe_handler.py new file mode 100644 index 0000000..47dca51 --- /dev/null +++ b/frontend/iva/handlers/compare_cpe_handler.py @@ -0,0 +1,37 @@ +from django.http import HttpResponse +from django.template import loader +from .request_handler_utils import * +from wfn.wfn_converter import WFNConverter +from wfn.wfn_comparator import compare_wfn + +template = loader.get_template('iva/compare_cpe.html') + + +def handle_request(request): + if is_get_request(request): + get_handler = GetHandler(request) + return get_handler.create_http_response() + + +wfn_converter = WFNConverter() + + +class GetHandler: + + def __init__(self, request): + self.request = request + + def create_http_response(self): + context = self.create_context() + return HttpResponse(template.render(context, self.request)) + + def create_context(self): + wfn_a = self.get_wfn_from_get_request('uri_binding_a') + wfn_b = self.get_wfn_from_get_request('uri_binding_b') + result = compare_wfn(wfn_a, wfn_b) + return {'wfn_a': wfn_a, 'wfn_b': wfn_b, 'coincidence': result.get('coincidence_rate'), + 'not_matches': result.get('not_matches')} + + def get_wfn_from_get_request(self, get_request_key): + uri_binding = self.request.GET.get(get_request_key) + return wfn_converter.convert_cpe_uri_to_wfn(uri_binding) diff --git a/frontend/iva/handlers/cpe_wfn_handler.py b/frontend/iva/handlers/cpe_wfn_handler.py new file mode 100644 index 0000000..532d448 --- /dev/null +++ b/frontend/iva/handlers/cpe_wfn_handler.py @@ -0,0 +1,26 @@ +from django.http import HttpResponse +from django.template import loader +from .request_handler_utils import * +from wfn.wfn_converter import WFNConverter + +template = loader.get_template('iva/cpe_wfn.html') + + +def handle_request(request): + if is_get_request(request): + get_handler = GetHandler(request) + return get_handler.create_http_response() + + +class GetHandler: + def __init__(self, request): + self.request = request + + def create_http_response(self): + context = self.create_context() + return HttpResponse(template.render(context, self.request)) + + def create_context(self): + wfn_converter = WFNConverter() + uri_binding = self.request.GET.get('uri_binding') + return {'uri_binging': uri_binding, 'wfn': wfn_converter.convert_cpe_uri_to_wfn(uri_binding)} diff --git a/frontend/iva/handlers/cve_matches_handler.py b/frontend/iva/handlers/cve_matches_handler.py new file mode 100644 index 0000000..c3d4af3 --- /dev/null +++ b/frontend/iva/handlers/cve_matches_handler.py @@ -0,0 +1,112 @@ +from django.http import HttpResponse +from django.template import loader +from inventory.software_cve_matches import CVEMatches +from inventory.inventory import Inventory +from .request_handler_utils import * +from config import get_cve_search_url + +template = loader.get_template('iva/cve_matches.html') + + +def handle_request(request): + if is_get_request(request): + get_handler = GetHandler(request) + get_handler.handle_get_request() + return get_handler.create_http_response() + elif is_post_request(request): + post_handler = PostHandler(request) + return post_handler.handle_post_request() + + +class PostHandler: + + def __init__(self, request): + self.request = request + self.option = self.post_value('option') + self.software_id = self.post_value('software_id') + self.cve_id = self.post_value('cve_id') + self.cve_matches = CVEMatches() + + def handle_post_request(self): + if self.option == 'remove_cve': + self.cve_matches.set_cve_match_as_removed(self.software_id, self.cve_id) + elif self.option == 'restore_cve': + self.cve_matches.restore_cve_match(self.software_id, self.cve_id) + elif self.option == 'set_cve_as_positive': + self.cve_matches.set_cve_match_as_positive(self.software_id, self.cve_id) + elif self.option == 'set_cve_as_negative': + self.cve_matches.set_cve_match_as_negative(self.software_id, self.cve_id) + return HttpResponse('ok') + + def post_value(self, key): + return self.request.POST.get(key) + + +class GetHandler: + + def __init__(self, request): + self.request = request + self.cve_matches = CVEMatches() + self.inventory = Inventory() + self.option = self.get_value('option') + self.vendor = self.get_value('vendor') + self.product = self.get_value('product') + self.vendors = self.get_vendors() + self.products = [] + + def get_vendors(self): + vendors = self.inventory.get_vendors() + vendors.append('all') + return vendors + + def handle_get_request(self): + pass + + def create_http_response(self): + return HttpResponse(template.render(self.create_context(), self.request)) + + def create_context(self): + if self.option == 'show_removed': + if self.vendor == 'all': + return self.get_context_dict(['show_removed'], False) + return self.get_context_dict(['show_removed'], True) + elif self.option == 'hide_removed': + if self.vendor == 'all': + return self.get_context_dict(['hide_removed'], False) + return self.get_context_dict(['hide_removed'], True) + elif self.option == 'ordered_by_year_desc_hide_removed': + if self.vendor == 'all': + return self.get_context_dict(['ordered_by_year_desc', 'hide_removed'], False) + return self.get_context_dict(['ordered_by_year_desc', 'hide_removed'], True) + elif self.option == 'ordered_by_year_desc_show_removed': + if self.vendor == 'all': + return self.get_context_dict(['ordered_by_year_desc', 'show_removed'], False) + return self.get_context_dict(['ordered_by_year_desc', 'show_removed'], True) + elif self.option == 'ordered_by_year_asc_hide_removed': + if self.vendor == 'all': + return self.get_context_dict(['ordered_by_year_asc', 'hide_removed'], False) + return self.get_context_dict(['ordered_by_year_asc', 'hide_removed'], True) + elif self.option == 'ordered_by_year_asc_show_removed': + if self.vendor == 'all': + return self.get_context_dict(['ordered_by_year_asc', 'show_removed'], False) + return self.get_context_dict(['ordered_by_year_asc', 'show_removed'], True) + elif self.option == 'show_vendor_cves': + return self.get_context_dict(['hide_removed'], True) + return self.get_context_dict(['hide_removed'], False) + + def get_context_dict(self, filters, vendor_cves): + if vendor_cves: + cve_matches = self.cve_matches.get_vendor_product_cve_matches(self.vendor, self.product, filters) + vendor = self.vendor + product = self.product + products = self.inventory.get_vendor_products(self.vendor) + else: + cve_matches = self.cve_matches.get_cve_matches(filters) + vendor = 'all' + product = 'all' + products = self.products + return {'cve_matches': cve_matches, 'vendors': self.vendors, 'products': products, 'vendor': vendor, + 'product': product, 'cve_search_url': get_cve_search_url()} + + def get_value(self, key): + return self.request.GET.get(key) diff --git a/frontend/iva/handlers/daily_db_update_handler.py b/frontend/iva/handlers/daily_db_update_handler.py new file mode 100644 index 0000000..d62b077 --- /dev/null +++ b/frontend/iva/handlers/daily_db_update_handler.py @@ -0,0 +1,122 @@ +from django.http import HttpResponse +from local_repositories.tasks.task_update_db import DailyUpdate +from local_repositories.tasks.task_populate_db import PopulateDB +from local_repositories.tasks.task_repopulate_db import RepopulateDB +from local_repositories.tasks.scheduler import TaskScheduler +from local_repositories.cve_search import CVESearchDB +from local_repositories.cpe_dict import CPEDict +from local_repositories.cve_feeds import CVEFeeds +from .request_handler_utils import * +from django.template import loader +import time + +TEMPLATE = loader.get_template('iva/local_repositories.html') + + +def handle_request(request): + handler = get_handler(request) + handler.handle_request() + return handler.create_http_response() + + +def get_handler(request): + if is_get_request(request): + return GetHandler(request) + return PostHandler(request) + + +def add_execution_hour(task): + execution_time = str(task.get('execution_time')).split(':') + task.update({'execution_hour': execution_time[0]}) + + +def add_execution_minute(task): + execution_time = str(task.get('execution_time')).split(':') + task.update({'execution_minute': execution_time[1]}) + + +class GetHandler: + + def __init__(self, request): + self.request = request + self.daily_db_update = DailyUpdate() + self.cve_search_db = CVESearchDB() + self.task_scheduler = TaskScheduler() + self.option = self.request.GET.get('option') + self.task_name = self.request.GET.get('name') + self.cpe_dict = CPEDict() + self.cve_feeds = CVEFeeds() + + def handle_request(self): + self.daily_db_update.create_task_in_db() + if not self.task_scheduler.is_task_scheduled(self.daily_db_update.name): + self.task_scheduler.schedule_task(self.daily_db_update) + + def create_http_response(self): + return HttpResponse(TEMPLATE.render({'task': self.get_task_info(), + 'db_populated': self.is_db_populated(), + 'cve_search_cpe_entries': self.cve_search_db.get_number_of_cpes_entries(), + 'cve_search_cve_entries': self.cve_search_db.get_number_of_cves_entries(), + 'iva_cpe_entries': self.cpe_dict.number_of_entries(), + 'iva_cve_entries': self.cve_feeds.number_of_entries(), + 'is_population_running': self.is_db_population_running(), + 'is_repopulation_running': self.is_db_repopulation_running()}, + self.request)) + + def is_db_populated(self): + if not self.is_db_population_running(): + return self.cve_search_db.is_cve_search_populated() + return False + + def is_db_population_running(self): + return self.task_scheduler.is_task_running('populate_db') + + def is_db_repopulation_running(self): + return self.task_scheduler.is_task_running('repopulate_db') + + def get_task_info(self): + task_info = self.daily_db_update.get_task_info() + add_execution_hour(task_info) + add_execution_minute(task_info) + self.add_is_scheduled(task_info) + self.add_is_running(task_info) + return task_info + + def add_is_scheduled(self, task_info): + if self.task_scheduler.is_task_scheduled(task_info.get('name')): + task_info.update({'is_scheduled': 'Yes'}) + else: + task_info.update({'is_scheduled': 'No'}) + + def add_is_running(self, task_info): + if self.task_scheduler.is_task_running(task_info.get('name')): + task_info.update({'is_running': 'Yes'}) + else: + task_info.update({'is_running': 'No'}) + + +class PostHandler: + + def __init__(self, request): + self.request = request + self.daily_db_update = DailyUpdate() + self.task_scheduler = TaskScheduler() + self.option = self.request.POST.get('option') + self.task_name = self.request.POST.get('name') + + def handle_request(self): + if self.option == 'activate': + self.daily_db_update.activate_task() + elif self.option == 'deactivate': + self.daily_db_update.deactivate_task() + self.task_scheduler.cancel_task(self.daily_db_update) + self.task_scheduler.reschedule_task(self.daily_db_update) + elif self.option == 'populate_db': + self.task_scheduler.schedule_task_now(PopulateDB()) + time.sleep(1) + elif self.option == 'repopulate_db': + self.task_scheduler.schedule_task_now(RepopulateDB()) + time.sleep(1) + + def create_http_response(self): + return HttpResponse('') diff --git a/frontend/iva/handlers/grouped_cve_matches_handler.py b/frontend/iva/handlers/grouped_cve_matches_handler.py new file mode 100644 index 0000000..2beadb0 --- /dev/null +++ b/frontend/iva/handlers/grouped_cve_matches_handler.py @@ -0,0 +1,88 @@ +from django.http import HttpResponse +from django.template import loader +from config import get_cve_search_url +from inventory.inventory import Inventory +from .request_handler_utils import * +from inventory.software_cve_matches import CVEMatches + +TEMPLATE = loader.get_template('iva/grouped_cve_matches.html') + + +def handle_request(request): + handler = get_handler(request) + handler.handle_request() + return handler.create_http_response() + + +def get_handler(request): + if is_get_request(request): + return GetHandler(request) + return PostHandler(request) + + +class GetHandler: + + def __init__(self, request): + self.request = request + self.software_id = self.request.GET.get('software_id') + self.cve_id = self.request.GET.get('cve_id') + self.grouped_cve_matches = [] + self.software = {} + + def handle_request(self): + self.software = self.get_software() + self.grouped_cve_matches = self.get_grouped_matches() + + def get_grouped_matches(self): + return CVEMatches().get_software_cve_matches_with_same_cpe_entries_as_cve(self.software_id, self.cve_id) + + def get_software(self): + return Inventory().get_software_by_id(self.software_id) + + def create_http_response(self): + return HttpResponse(TEMPLATE.render(self.create_response_context(), self.request)) + + def create_response_context(self): + return {'cve_matches': self.grouped_cve_matches, + 'software': self.software, + 'cve_search_url': get_cve_search_url()} + + +class PostHandler: + + def __init__(self, request): + self.cve_matches = CVEMatches() + self.request = request + self.software_id = self.post_value('software_id') + self.cve_id_master = self.post_value('cve_id_master') + self.option = self.post_value('option') + + def handle_request(self): + if self.option == 'set_group_as_positive': + self.set_group_as_positive() + elif self.option == 'set_group_as_negative': + self.set_group_as_negative() + elif self.option == 'set_group_as_removed': + self.set_group_as_removed() + elif self.option == 'restore_group': + self.restore_group() + + def set_group_as_positive(self): + self.cve_matches.set_cve_matches_group_as_positive(self.software_id, self.cve_id_master) + + def set_group_as_negative(self): + self.cve_matches.set_cve_matches_group_as_negative(self.software_id, self.cve_id_master) + + def set_group_as_removed(self): + self.cve_matches.set_cve_matches_group_as_removed(self.software_id, self.cve_id_master) + + def restore_group(self): + self.cve_matches.restore_cve_matches_group(self.software_id, self.cve_id_master) + + def create_http_response(self): + return HttpResponse('OK') + + def post_value(self, key): + return self.request.POST.get(key) + + diff --git a/frontend/iva/handlers/index_handler.py b/frontend/iva/handlers/index_handler.py new file mode 100644 index 0000000..28813eb --- /dev/null +++ b/frontend/iva/handlers/index_handler.py @@ -0,0 +1,26 @@ +from django.http import HttpResponse +from .request_handler_utils import * +from django.template import loader + +template = loader.get_template('iva/index.html') + + +def handle_request(request): + if is_get_request(request): + get_handler = GetHandler(request) + context = get_handler.get_context_for_get_request() + http_response = get_handler.create_http_response(context) + return http_response + + +class GetHandler: + def __init__(self, request): + self.request = request + + def get_context_for_get_request(self): + return {} + + def create_http_response(self, context): + return HttpResponse(template.render(context, self.request)) + + diff --git a/frontend/iva/handlers/modify_cpe_handler.py b/frontend/iva/handlers/modify_cpe_handler.py new file mode 100644 index 0000000..934c4cb --- /dev/null +++ b/frontend/iva/handlers/modify_cpe_handler.py @@ -0,0 +1,72 @@ +import pycountry +from django.http import HttpResponse +from django.template import loader +from inventory.inventory import Inventory +from inventory.software_cpe import SoftwareCPE +from .request_handler_utils import * +from wfn.wfn_converter import WFNConverter + +template = loader.get_template('iva/modify_cpe.html') + + +def handle_request(request): + if is_get_request(request): + get_handler = GetHandler(request) + return get_handler.create_http_response() + elif is_post_request(request): + post_handler = PostHandler(request) + post_handler.handle_post_request() + http_response = post_handler.create_http_response() + return http_response + + +class GetHandler: + + def __init__(self, request): + self.request = request + self.software_id = self.request.GET.get('software_id') + + def create_http_response(self): + return HttpResponse(template.render(self.create_context(), self.request)) + + def create_context(self): + return get_context_dict(self.get_software(), update=False) + + def get_software(self): + return Inventory().get_software_by_id(self.software_id) + + +class PostHandler: + + def __init__(self, request): + self.request = request + self.software_id = self.request.POST.get('software_id') + + def handle_post_request(self): + SoftwareCPE().update_software_cpe(self.software_id, self.get_wfn()) + + def get_wfn(self): + return WFNConverter().create_wfn_from_user_input(self.request.POST) + + def create_http_response(self): + return HttpResponse(template.render(get_context_dict(self.get_software(), update=True), self.request)) + + def get_software(self): + return Inventory().get_software_by_id(self.software_id) + + +def get_wfn_from_sw(software): + return software.get('cpe').get('wfn') + + +def get_uri_from_sw(software): + return software.get('cpe').get('uri') + + +def get_context_dict(software, update): + return {'software': software, + 'software_id': software.get('id'), + 'uri_binding': get_uri_from_sw(software), + 'wfn': get_wfn_from_sw(software), + 'updated': update, + 'lang_codes': pycountry.languages} \ No newline at end of file diff --git a/frontend/iva/handlers/modify_user_handler.py b/frontend/iva/handlers/modify_user_handler.py new file mode 100644 index 0000000..4f994cd --- /dev/null +++ b/frontend/iva/handlers/modify_user_handler.py @@ -0,0 +1,56 @@ +from django.http import HttpResponse +from django.template import loader + +from user_authentication.user import User +from .request_handler_utils import * + +TEMPLATE = loader.get_template('iva/modify_user.html') +USERS_TEMPLATE = loader.get_template('iva/users.html') + + +def handle_request(request): + handler = get_handler(request) + http_response = handler.handle_request() + return http_response + + +def get_handler(request): + if is_get_request(request): + return GetHandler(request) + return PostHandler(request) + + +class GetHandler: + + def __init__(self, request): + self.request = request + self.username = self.request.GET.get('username') + self.surname = self.request.GET.get('surname') + self.name = self.request.GET.get('name') + self.user = User() + + def handle_request(self): + if self.request.GET.get('option') == 'check_user': + if self.user.exist_user_with_username(self.username): + return HttpResponse({'exist'}, self.request) + return HttpResponse({'not_exist'}, self.request) + return HttpResponse(TEMPLATE.render({'old_username': self.username, + 'name': self.name, + 'surname': self.surname}, self.request)) + + +class PostHandler: + + def __init__(self, request): + self.request = request + self.old_username = self.request.POST.get('old_username') + self.username = self.request.POST.get('username') + self.surname = self.request.POST.get('surname') + self.name = self.request.POST.get('name') + self.user = User() + + def handle_request(self): + self.user.modify_user(self.old_username, self.username, self.name, self.surname) + return HttpResponse('') + diff --git a/frontend/iva/handlers/new_software_handler.py b/frontend/iva/handlers/new_software_handler.py new file mode 100644 index 0000000..9d04363 --- /dev/null +++ b/frontend/iva/handlers/new_software_handler.py @@ -0,0 +1,81 @@ +from django.http import HttpResponse +from django.template import loader +from .request_handler_utils import * +from inventory.inventory import Inventory + +template = loader.get_template('iva/new_software.html') + + +def handle_request(request): + handler = get_handler(request) + response = handler.handle_request() + return response + + +def get_handler(request): + if is_get_request(request): + return GetHandler(request) + return PostHandler(request) + + +class GetHandler: + + def __init__(self, request): + self.inventory = Inventory() + self.request = request + self.option = self.request.GET.get('option') + self.read_inventory_error = False + + def handle_request(self): + self.handle_get_options() + return self.create_http_response() + + def handle_get_options(self): + if self.option == 'read_inventory': + self.read_inventory() + + def create_http_response(self): + return HttpResponse(self.render_template()) + + def read_inventory(self): + try: + self.inventory.insert_new_software_products_to_db() + except: + self.read_inventory_error = True + + def render_template(self): + return template.render(self.create_context(), self.request) + + def create_context(self): + return {'items': self.get_new_software(), 'read_inventory_error': self.read_inventory_error} + + def get_new_software(self): + return self.inventory.get_software_products_without_assigned_cpe() + + +class PostHandler: + + def __init__(self, request): + self.request = request + self.inventory = Inventory() + self.product_to_search = self.request.POST.get('product_to_search') + + def handle_request(self): + software_list = self.get_new_software() + req_context = create_context(software_list) + req_template = self.create_template(req_context) + return create_http_response(req_template) + + def create_template(self, req_context): + return template.render(req_context, self.request) + + def get_new_software(self): + return self.inventory.search_software_products_without_assigned_cpe(self.product_to_search) + + +def create_context(new_software_list): + return {'items': new_software_list, 'read_inventory_error': False} + + +def create_http_response(template_): + return HttpResponse(template_) \ No newline at end of file diff --git a/frontend/iva/handlers/request_handler_utils.py b/frontend/iva/handlers/request_handler_utils.py new file mode 100644 index 0000000..5367dcd --- /dev/null +++ b/frontend/iva/handlers/request_handler_utils.py @@ -0,0 +1,16 @@ +import ast + + +def is_get_request(request): + return request.method == 'GET' + + +def is_post_request(request): + return request.method == 'POST' + + +def create_dict_from_string(string): + try: + return ast.literal_eval(string) + except: + return False diff --git a/frontend/iva/handlers/search_cves_handler.py b/frontend/iva/handlers/search_cves_handler.py new file mode 100644 index 0000000..b7c95f3 --- /dev/null +++ b/frontend/iva/handlers/search_cves_handler.py @@ -0,0 +1,83 @@ +from django.http import HttpResponse +from django.template import loader +from inventory.inventory import Inventory +from inventory.software_cve_matches import CVEMatches +from matching.cve_matcher import CVEMatcher +from .request_handler_utils import * +import config + +template = loader.get_template('iva/search_cves.html') + + +def handle_request(request): + + if is_get_request(request): + get_handler = GetHandler(request) + get_handler.handle_get_request() + context = get_handler.create_context() + return get_handler.create_http_response(context) + elif is_post_request(request): + post_handler = PostHandler() + return post_handler.create_http_response() + + +class GetHandler: + + def __init__(self, request): + self.request = request + self.cve_matches = CVEMatches() + self.cve_matcher = CVEMatcher() + self.uri_binding = self.get_uri_binding_from_get_request() + self.software_id = self.get_software_id_from_get_request() + + def create_http_response(self, context): + return HttpResponse(template.render(context, self.request)) + + def handle_get_request(self): + pass + + def get_uri_binding_from_get_request(self): + return self.request.GET.get('uri_binding') + + def get_software_id_from_get_request(self): + return self.request.GET.get('software_id') + + def get_option_from_get_request(self): + return self.request.GET.get('option') + + def get_cve_id_from_get_request(self): + return self.request.GET.get('cve_id') + + def create_context(self): + return {'cve_matches': self.get_cpe_cve_matches(), + 'uri_binging': self.uri_binding, + 'software_id': self.software_id, + 'software': self.get_software(), + 'cve_search_url': config.get_cve_search_url()} + + def get_cpe_cve_matches(self): + matches = self.search_matches() + self.insert_new_matches(matches) + return self.get_new_matches() + + def search_matches(self): + return self.cve_matcher.search_cves_for_cpe(self.uri_binding) + + def insert_new_matches(self, matches): + self.cve_matches.insert_software_cve_matches(self.software_id, matches) + + def get_new_matches(self): + return self.cve_matches.get_software_cve_matches(self.software_id) + + def get_software(self): + return Inventory().get_software_by_id(self.software_id) + + +class PostHandler: + + def __init__(self): + pass + + @staticmethod + def create_http_response(): + return HttpResponse('POST Request made', content_type="text/plain") diff --git a/frontend/iva/handlers/sign_in_handler.py b/frontend/iva/handlers/sign_in_handler.py new file mode 100644 index 0000000..4e3b365 --- /dev/null +++ b/frontend/iva/handlers/sign_in_handler.py @@ -0,0 +1,53 @@ +from django.http import HttpResponse +from django.template import loader + +from user_authentication.user import User +from .request_handler_utils import * + +USER_SESSION_KEY = 'user' +SIGN_IN_TEMPLATE = loader.get_template('iva/sign_in.html') +INDEX_TEMPLATE = loader.get_template('iva/index.html') + + +def handle_request(request): + handler = get_handler(request) + handler.handle_request() + return handler.create_http_response() + + +def get_handler(request): + if is_get_request(request): + return GetHandler(request) + return PostHandler(request) + + +class GetHandler: + + def __init__(self, request): + self.request = request + + def handle_request(self): + if self.request.GET.get('sign_out') and USER_SESSION_KEY in self.request.session: + del self.request.session[USER_SESSION_KEY] + self.request.session.flush() + + def create_http_response(self): + return HttpResponse(SIGN_IN_TEMPLATE.render({'is_valid_user': True}, self.request)) + + +class PostHandler: + + def __init__(self, request): + self.request = request + self.username = self.request.POST.get('username') + self.pwd = self.request.POST.get('password') + self.user = User() + + def handle_request(self): + if self.user.verify_user(self.username, self.pwd): + self.request.session[USER_SESSION_KEY] = self.user.username + + def create_http_response(self): + if USER_SESSION_KEY in self.request.session: + return HttpResponse(INDEX_TEMPLATE.render({}, self.request)) + return HttpResponse(SIGN_IN_TEMPLATE.render({'is_valid_user': False}, self.request)) diff --git a/frontend/iva/handlers/sw_products_with_cpe_handler.py b/frontend/iva/handlers/sw_products_with_cpe_handler.py new file mode 100644 index 0000000..70a5fc6 --- /dev/null +++ b/frontend/iva/handlers/sw_products_with_cpe_handler.py @@ -0,0 +1,27 @@ +from django.http import HttpResponse +from django.template import loader +from inventory.software_cpe import SoftwareCPE +from inventory.inventory import Inventory +from .request_handler_utils import * + +template = loader.get_template('iva/sw_products_with_cpe.html') + + +def handle_request(request): + if is_get_request(request): + get_handler = GetHandler(request) + return get_handler.create_http_response() + + +class GetHandler: + def __init__(self, request): + self.cpe_inventory = SoftwareCPE() + self.request = request + self.inventory = Inventory() + + def create_http_response(self): + context = self.create_context() + return HttpResponse(template.render(context, self.request)) + + def create_context(self): + return {'inventory': self.inventory.get_software_products_with_assigned_cpe()} diff --git a/frontend/iva/handlers/users_handler.py b/frontend/iva/handlers/users_handler.py new file mode 100644 index 0000000..d5afb9f --- /dev/null +++ b/frontend/iva/handlers/users_handler.py @@ -0,0 +1,55 @@ +from django.http import HttpResponse +from django.template import loader + +from user_authentication.user import User +from .request_handler_utils import * + +TEMPLATE = loader.get_template('iva/users.html') + + +def handle_request(request): + handler = get_handler(request) + handler.handle_request() + return handler.create_http_response() + + +def get_handler(request): + if is_get_request(request): + return GetHandler(request) + return PostHandler(request) + + +class GetHandler: + + def __init__(self, request): + self.request = request + self.option = self.request.GET.get('option') + self.username = self.request.GET.get('username') + self.user = User() + + def handle_request(self): + pass + + def create_http_response(self): + if self.option == 'pwd_changed': + return HttpResponse(TEMPLATE.render(self.get_context_dict(), self.request)) + return HttpResponse(TEMPLATE.render({'users': self.user.get_users()}, self.request)) + + def get_context_dict(self): + return {'users': self.user.get_users(), 'pwd_changed': True, 'username': self.username} + + +class PostHandler: + + def __init__(self, request): + self.request = request + self.option = self.request.POST.get('option') + self.username = self.request.POST.get('username') + self.user = User() + + def handle_request(self): + if self.option == 'delete': + self.user.delete_user(self.username) + + def create_http_response(self): + return HttpResponse('OK') diff --git a/frontend/iva/migrations/__init__.py b/frontend/iva/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/frontend/iva/models.py b/frontend/iva/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/frontend/iva/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/frontend/iva/static/iva/css/alerts.css b/frontend/iva/static/iva/css/alerts.css new file mode 100644 index 0000000..e20a410 --- /dev/null +++ b/frontend/iva/static/iva/css/alerts.css @@ -0,0 +1,13 @@ +.alert_status { + border-radius: 3px; + padding: 3px; +} + +.alert_status a { + text-decoration: none; + color: white; +} + +.alert_status a:hover { + font-weight: bold; +} \ No newline at end of file diff --git a/frontend/iva/static/iva/css/compare_cpe.css b/frontend/iva/static/iva/css/compare_cpe.css new file mode 100644 index 0000000..1c3988c --- /dev/null +++ b/frontend/iva/static/iva/css/compare_cpe.css @@ -0,0 +1,14 @@ +#comparison-table { + font-family: Verdana, serif; + font-size: 12px; +} + +#comparison-table td { + background-color: white; + padding: 5px; +} + +.attribute-label { + color: #2e6da4; + font-weight: bold; +} \ No newline at end of file diff --git a/frontend/iva/static/iva/css/cpe_form.css b/frontend/iva/static/iva/css/cpe_form.css new file mode 100644 index 0000000..20faafe --- /dev/null +++ b/frontend/iva/static/iva/css/cpe_form.css @@ -0,0 +1,14 @@ +div#form_add_cpe { + width: 100%; + height: 245px; +} + +div#form_container_right { + width: 50%; + float: left; +} + +div#form_container_left { + width: 29%; + float: left; +} \ No newline at end of file diff --git a/frontend/iva/static/iva/css/jplist.core.min.css b/frontend/iva/static/iva/css/jplist.core.min.css new file mode 100644 index 0000000..743f2bd --- /dev/null +++ b/frontend/iva/static/iva/css/jplist.core.min.css @@ -0,0 +1 @@ +.lato{font-family:'Lato',sans-serif}.jplist-hidden{display:none}.jplist-sticky{position:fixed;width:100%;left:0;top:0;z-index:1000;background:#f1f1f1;padding-bottom:5px}.sticky-demo .panel>.center{max-width:100%}.sticky-demo .jplist-sticky>.center{max-width:95%}.no-cssgradients .jplist-panel button:hover{background:#f4f4f4}.jplist-panel{color:#27252a}.jplist-panel button{float:left;height:30px;box-shadow:0 0 1px #fff;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;border:1px solid #dbdbdb;border-radius:3px;text-shadow:1px 1px 1px #fff;color:#27252a;background:#fff;background:-moz-linear-gradient(top, #fff 0, #efefef 100%);background:-webkit-gradient(linear, left top, left bottom, color-stop(0, #fff), color-stop(100%, #efefef));background:-webkit-linear-gradient(top, #fff 0, #efefef 100%);background:-o-linear-gradient(top, #fff 0, #efefef 100%);background:-ms-linear-gradient(top, #fff 0, #efefef 100%);background:linear-gradient(to bottom, #fff 0, #efefef 100%);margin:10px 10px 0 0}.jplist-panel button:hover{color:#0381bb;background:#f2f2f2;background:-moz-linear-gradient(top, #fafafa 0, #ededed 100%);background:-webkit-gradient(linear, left top, left bottom, color-stop(0, #fafafa), color-stop(100%, #ededed));background:-webkit-linear-gradient(top, #fafafa 0, #ededed 100%);background:-o-linear-gradient(top, #fafafa 0, #ededed 100%);background:-ms-linear-gradient(top, #fafafa 0, #ededed 100%);background:linear-gradient(to bottom, #fafafa 0, #ededed 100%)}.jplist-panel button.jplist-disabled{background:#e3e3e3;color:#6c6c6c;cursor:default}.jplist-panel button.jplist-selected{color:#f03800}.jplist-panel input[type="text"]{float:left;height:30px;box-shadow:0 0 1px #fff;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;border:1px solid #dbdbdb;border-radius:3px;text-shadow:1px 1px 1px #fff;color:#27252a;width:150px;text-indent:5px;background:#fff}.jplist-panel input[type="checkbox"],.jplist-panel input[type="radio"]{float:left;display:inline-block;height:30px;background:#fff;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;color:#27252a}.jplist-panel label{float:left;height:30px;line-height:30px;padding:0 5px;display:inline-block;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;color:#27252a;cursor:pointer}.jplist-icon{float:left;height:30px;box-shadow:0 0 1px #fff;background:#fff;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;border:1px solid #dbdbdb;border-radius:3px;text-shadow:1px 1px 1px #fff;color:#27252a;display:inline-block;line-height:30px;padding:0 10px;border-right:0}.jplist-icon:hover{color:#0381bb}.jplist-label{float:left;height:30px;box-shadow:0 0 1px #fff;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;border:1px solid #dbdbdb;border-radius:3px;text-shadow:1px 1px 1px #fff;color:#27252a;line-height:30px;padding:0 10px;margin:10px 10px 0 0;background:#fff;background:-moz-linear-gradient(top, #fff 0, #efefef 100%);background:-webkit-gradient(linear, left top, left bottom, color-stop(0, #fff), color-stop(100%, #efefef));background:-webkit-linear-gradient(top, #fff 0, #efefef 100%);background:-o-linear-gradient(top, #fff 0, #efefef 100%);background:-ms-linear-gradient(top, #fff 0, #efefef 100%);background:linear-gradient(to bottom, #fff 0, #efefef 100%)}.jplist-selected{color:#CA3F1A}.jplist-box{float:left;height:30px;margin:10px 10px 0 0}@media only screen and (max-width:600px){.jplist-panel{display:none}}.jplist-drop-down{float:left;height:30px;box-shadow:0 0 1px #fff;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;border:1px solid #dbdbdb;border-radius:3px;text-shadow:1px 1px 1px #fff;color:#27252a;width:150px;background:#fff;background:-moz-linear-gradient(top, #fff 0, #efefef 100%);background:-webkit-gradient(linear, left top, left bottom, color-stop(0, #fff), color-stop(100%, #efefef));background:-webkit-linear-gradient(top, #fff 0, #efefef 100%);background:-o-linear-gradient(top, #fff 0, #efefef 100%);background:-ms-linear-gradient(top, #fff 0, #efefef 100%);background:linear-gradient(to bottom, #fff 0, #efefef 100%);margin:10px 10px 0 0;position:relative}.jplist-drop-down .jplist-dd-panel{float:left;width:150px;height:30px;line-height:30px;overflow:hidden;text-indent:10px;cursor:pointer}.jplist-drop-down .jplist-dd-panel:after{content:'\25BC';color:#111;display:inline-block;position:absolute;right:10px;z-index:1000}.jplist-drop-down ul{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;border:1px solid #dbdbdb;border-radius:0 0 3px 3px;position:relative;top:0;z-index:1000;width:148px;display:none;overflow:hidden;background:#fff;list-style:none;margin:0;padding:0;box-shadow:0 3px 6px #bbb}.jplist-drop-down ul li{width:100%;float:left;height:30px;line-height:30px;text-indent:10px;cursor:pointer}.jplist-drop-down ul li:hover{background-color:#ececec}.jplist-drop-down:hover .jplist-dd-panel{color:#0381bb}.jplist-drop-down:hover .jplist-dd-panel:after{color:#0381bb}.jplist-select{height:30px;box-shadow:0 0 1px #fff;background:#fff;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;border:1px solid #dbdbdb;border-radius:3px;text-shadow:1px 1px 1px #fff;color:#27252a;float:left;margin:10px 10px 0 0;padding:5px;text-shadow:none}.jplist-ios-button{display:none}@media only screen and (max-width:600px){.jplist-ios-button{display:block;width:100%;text-align:center;cursor:pointer;font-size:15px;line-height:30px;float:left;height:30px;box-shadow:0 0 1px #fff;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;border:1px solid #dbdbdb;border-radius:3px;text-shadow:1px 1px 1px #fff;color:#27252a;background:#fff;background:-moz-linear-gradient(top, #fff 0, #efefef 100%);background:-webkit-gradient(linear, left top, left bottom, color-stop(0, #fff), color-stop(100%, #efefef));background:-webkit-linear-gradient(top, #fff 0, #efefef 100%);background:-o-linear-gradient(top, #fff 0, #efefef 100%);background:-ms-linear-gradient(top, #fff 0, #efefef 100%);background:linear-gradient(to bottom, #fff 0, #efefef 100%)}.jplist-ios-button:hover{background:#f2f2f2;background:-moz-linear-gradient(top, #fafafa 0, #ededed 100%);background:-webkit-gradient(linear, left top, left bottom, color-stop(0, #fafafa), color-stop(100%, #ededed));background:-webkit-linear-gradient(top, #fafafa 0, #ededed 100%);background:-o-linear-gradient(top, #fafafa 0, #ededed 100%);background:-ms-linear-gradient(top, #fafafa 0, #ededed 100%);background:linear-gradient(to bottom, #fafafa 0, #ededed 100%)}.jplist-ios-show{display:block}} \ No newline at end of file diff --git a/frontend/iva/static/iva/css/jplist.textbox-filter.min.css b/frontend/iva/static/iva/css/jplist.textbox-filter.min.css new file mode 100644 index 0000000..0156b23 --- /dev/null +++ b/frontend/iva/static/iva/css/jplist.textbox-filter.min.css @@ -0,0 +1 @@ +.lato{font-family:'Lato',sans-serif}.jplist-panel .text-filter-box{height:30px;float:left;margin:10px 10px 0 0}.jplist-panel .text-filter-box .jplist-icon{background:#E8EFF9}.jplist-panel .text-filter-box .jplist-label{margin:0;border-right:0;background:#f1f1f1}.jplist-panel .text-filter-box button{margin:0;border-left:0;padding:0 10px} \ No newline at end of file diff --git a/frontend/iva/static/iva/css/main_template.css b/frontend/iva/static/iva/css/main_template.css new file mode 100644 index 0000000..dd5a355 --- /dev/null +++ b/frontend/iva/static/iva/css/main_template.css @@ -0,0 +1,313 @@ +body { + font-family: Verdana, serif; + font-size: 12px; + margin: 20px; + +} + +div#navigation-bar { + font-size: 14px; + padding-bottom: 8px; + color: #9d9d9d; + margin-left: 10px; +} + +div#navigation-bar a { + text-decoration: none; + color: #2e6da4; +} + +div#navigation-bar a:hover { + font-weight: bold; +} + +div#header { + height: 80px; + background-color: #f1f1f1; + border: 1px solid #ccc; + border-bottom: 0; + border-radius: 10px 10px 0px 0px; + padding: 20px; +} + +div#footer { + height: 30px; + background-color: #f1f1f1; + border:1px solid #ccc; + border-radius: 0px 0px 10px 10px; + padding: 20px; +} + +div#menu { + background-color:#5e5e5e; + height: 28px; + padding: 2.3px 5px 5px; +} + +.menu_option { + font-size: 16px; + float: left; + padding: 5px; +} + +.menu_option a { + color: #ffffff; + text-decoration: none; + padding: 5px; +} + +.menu_option a:hover { + background-color: #f1f1f1; + color: #5e5e5e; + border-radius: 4px 4px 4px 4px; + border: 1px solid #5e5e5e; +} + +.menu_option_logout { + font-size: 14px; + float: right; + color: #ffffff; + padding-top: 2px; +} + +div#content { + padding: 20px; + border-left: 1px solid #ccc; + border-right: 1px solid #ccc; +} + +.container-title { + height: 25px; + border-radius: 7px 7px 0px 0px; + background-color: #2e6da4; + padding-top: 5px; + padding-left: 5px; + color: #ffffff; + font-weight: bold; + font-size: 14px; + margin-left: 10px; + margin-right: 10px; + margin-top: 10px; +} + +.container { + border-radius: 0px 0px 7px 7px; + border: 1px solid #ccc; + border-top: 0px; + padding: 12px; + margin-left: 10px; + margin-right: 10px; + margin-bottom: 10px; +} + +.container button { + border:1px solid #2e6da4; + background:none; + outline:none; + padding:5px; + border-radius: 4px 4px 4px 4px; + color: #2e6da4; + font-weight: bold; + cursor: pointer; +} + +.container button:hover { + border:0; + color: #ffffff; + background: #2e6da4 none; + text-shadow: none; +} + +div#form_element { + text-align: left; + height: 35px; +} + +div#form_label { + width: 120px; + float: left; + padding-top: 2.2px; + padding-bottom: 2.3px; + padding-left: 5px; + background-color: #f1f1f1; + border-radius: 5px 0px 0px 5px; + border: 0.8px solid #204d74; + font-weight: normal; + color: #204d74; +} + +select#form_select { + width: 143px; + height: 24px; + border: 0.8px solid #204d74; + border-left: 0; + padding: 0px 1px 1px 0px; +} + +div#form_element input { + height: 15px; + border: 0.8px solid #204d74; + border-left: 0; + padding: 2px 2px 2px 3px; +} + +@-moz-document url-prefix() { + div#form_element input { + padding: 3px 2px 3px 3px; + } + select#form_select { + height: 23px; + } +} + +div#form_element input:focus { + height: 18px; + background-color: #f1f1f1; + border: 0; +} + +.form_element_text { + padding-left: 5px; + padding-top: 3px; + padding-right: 5px; + float: left; + color: #5e5e5e; +} + +tr:nth-child(even) { + background-color: #f2f2f2 +} + +td { + padding: 1px 5px; +} + +th { + background-color: #f4f4f4; + color: #5e5e5e; + border-radius: 5px 5px 5px 5px; + padding: 4px; +} + +th a { + text-decoration: none; +} + +th a:hover { + cursor: pointer; +} + +img { + width: 20px; + height: 20px; +} + +.test-panel { + background-color: #2b542c +} + +.jplist-label { + border: 0.8px solid #204d74; + font-weight: normal; + color: #204d74; +} + +.ok-msg { + background-color: green; + color: white; + padding: 5px; + border-radius: 5px 5px 5px 5px; + font-size: 12px; + text-align: center; + font-weight: bold; +} + +.ok-msg { + background-color: green; + color: white; + padding: 5px; + border-radius: 5px 5px 5px 5px; + font-size: 12px; + text-align: center; + font-weight: bold; +} + +.warning-msg { + color: #9F6000; + background-color: #FEEFB3; + padding: 5px; + border-radius: 5px 5px 5px 5px; + font-size: 12px; + text-align: center; + font-weight: bold; +} + + +.error-msg { + background-color: #d10000; + color: white; + padding: 5px; + border-radius: 5px 5px 5px 5px; + font-size: 12px; + text-align: center; + font-weight: bold; +} + +.info-msg { + background-color: #f4f4f4; + border: 0.8px solid #204d74; + color: #666666; + padding: 5px; + border-radius: 5px 5px 5px 5px; + font-size: 12px; + text-align: center; + font-weight: bold; +} + + /* Tooltip container */ +.tooltip { + position: relative; + display: inline-block; + border-bottom: 1px dotted black; /* If you want dots under the hoverable text */ +} + +/* Tooltip text */ +.tooltip .tooltiptext { + font-weight: normal; + visibility: hidden; + width: 380px; + background-color: #555; + color: #fff; + text-align: left; + padding: 5px; + border-radius: 6px; + + /* Position the tooltip text */ + position: absolute; + z-index: 1; + bottom: 140%; + left: 50%; + margin-left: -40px; + + /* Fade in tooltip */ + opacity: 0; + transition: opacity 1s; +} + +/* Tooltip arrow */ +.tooltip .tooltiptext::after { + content: ""; + position: absolute; + top: 100%; + left: 13%; + margin-left: -5px; + border-width: 5px; + border-style: solid; + border-color: #555 transparent transparent transparent; +} + +/* Show the tooltip text when you mouse over the tooltip container */ +.tooltip:hover .tooltiptext { + visibility: visible; + opacity: 1; +} \ No newline at end of file diff --git a/frontend/iva/static/iva/img/add.png b/frontend/iva/static/iva/img/add.png new file mode 100644 index 0000000..d99cfec Binary files /dev/null and b/frontend/iva/static/iva/img/add.png differ diff --git a/frontend/iva/static/iva/img/add_user.png b/frontend/iva/static/iva/img/add_user.png new file mode 100644 index 0000000..9cd61b3 Binary files /dev/null and b/frontend/iva/static/iva/img/add_user.png differ diff --git a/frontend/iva/static/iva/img/change_pwd.png b/frontend/iva/static/iva/img/change_pwd.png new file mode 100644 index 0000000..09588a8 Binary files /dev/null and b/frontend/iva/static/iva/img/change_pwd.png differ diff --git a/frontend/iva/static/iva/img/change_time.png b/frontend/iva/static/iva/img/change_time.png new file mode 100644 index 0000000..42e12e7 Binary files /dev/null and b/frontend/iva/static/iva/img/change_time.png differ diff --git a/frontend/iva/static/iva/img/close_eye.png b/frontend/iva/static/iva/img/close_eye.png new file mode 100644 index 0000000..570caef Binary files /dev/null and b/frontend/iva/static/iva/img/close_eye.png differ diff --git a/frontend/iva/static/iva/img/compare.png b/frontend/iva/static/iva/img/compare.png new file mode 100644 index 0000000..9defc1d Binary files /dev/null and b/frontend/iva/static/iva/img/compare.png differ diff --git a/frontend/iva/static/iva/img/delete.png b/frontend/iva/static/iva/img/delete.png new file mode 100644 index 0000000..159cd17 Binary files /dev/null and b/frontend/iva/static/iva/img/delete.png differ diff --git a/frontend/iva/static/iva/img/down.png b/frontend/iva/static/iva/img/down.png new file mode 100644 index 0000000..be340b9 Binary files /dev/null and b/frontend/iva/static/iva/img/down.png differ diff --git a/frontend/iva/static/iva/img/edit.png b/frontend/iva/static/iva/img/edit.png new file mode 100644 index 0000000..3f42699 Binary files /dev/null and b/frontend/iva/static/iva/img/edit.png differ diff --git a/frontend/iva/static/iva/img/glpi.jpg b/frontend/iva/static/iva/img/glpi.jpg new file mode 100644 index 0000000..ad3031e Binary files /dev/null and b/frontend/iva/static/iva/img/glpi.jpg differ diff --git a/frontend/iva/static/iva/img/group.png b/frontend/iva/static/iva/img/group.png new file mode 100644 index 0000000..2d15372 Binary files /dev/null and b/frontend/iva/static/iva/img/group.png differ diff --git a/frontend/iva/static/iva/img/help.png b/frontend/iva/static/iva/img/help.png new file mode 100644 index 0000000..1900eda Binary files /dev/null and b/frontend/iva/static/iva/img/help.png differ diff --git a/frontend/iva/static/iva/img/help_white.png b/frontend/iva/static/iva/img/help_white.png new file mode 100644 index 0000000..86dc8aa Binary files /dev/null and b/frontend/iva/static/iva/img/help_white.png differ diff --git a/frontend/iva/static/iva/img/log.png b/frontend/iva/static/iva/img/log.png new file mode 100644 index 0000000..2b8d846 Binary files /dev/null and b/frontend/iva/static/iva/img/log.png differ diff --git a/frontend/iva/static/iva/img/logout.png b/frontend/iva/static/iva/img/logout.png new file mode 100644 index 0000000..5b521eb Binary files /dev/null and b/frontend/iva/static/iva/img/logout.png differ diff --git a/frontend/iva/static/iva/img/match.png b/frontend/iva/static/iva/img/match.png new file mode 100644 index 0000000..ea58c4f Binary files /dev/null and b/frontend/iva/static/iva/img/match.png differ diff --git a/frontend/iva/static/iva/img/more_info.png b/frontend/iva/static/iva/img/more_info.png new file mode 100644 index 0000000..8c61182 Binary files /dev/null and b/frontend/iva/static/iva/img/more_info.png differ diff --git a/frontend/iva/static/iva/img/not_match.png b/frontend/iva/static/iva/img/not_match.png new file mode 100644 index 0000000..557b3e2 Binary files /dev/null and b/frontend/iva/static/iva/img/not_match.png differ diff --git a/frontend/iva/static/iva/img/notes.png b/frontend/iva/static/iva/img/notes.png new file mode 100644 index 0000000..42a7a69 Binary files /dev/null and b/frontend/iva/static/iva/img/notes.png differ diff --git a/frontend/iva/static/iva/img/search.png b/frontend/iva/static/iva/img/search.png new file mode 100644 index 0000000..1a2ca89 Binary files /dev/null and b/frontend/iva/static/iva/img/search.png differ diff --git a/frontend/iva/static/iva/img/see.png b/frontend/iva/static/iva/img/see.png new file mode 100644 index 0000000..1bad827 Binary files /dev/null and b/frontend/iva/static/iva/img/see.png differ diff --git a/frontend/iva/static/iva/img/send_email.png b/frontend/iva/static/iva/img/send_email.png new file mode 100644 index 0000000..876b951 Binary files /dev/null and b/frontend/iva/static/iva/img/send_email.png differ diff --git a/frontend/iva/static/iva/img/task_active.png b/frontend/iva/static/iva/img/task_active.png new file mode 100644 index 0000000..44fd80d Binary files /dev/null and b/frontend/iva/static/iva/img/task_active.png differ diff --git a/frontend/iva/static/iva/img/task_inactive.png b/frontend/iva/static/iva/img/task_inactive.png new file mode 100644 index 0000000..47dee22 Binary files /dev/null and b/frontend/iva/static/iva/img/task_inactive.png differ diff --git a/frontend/iva/static/iva/img/undo.png b/frontend/iva/static/iva/img/undo.png new file mode 100644 index 0000000..fb6f94f Binary files /dev/null and b/frontend/iva/static/iva/img/undo.png differ diff --git a/frontend/iva/static/iva/img/up.png b/frontend/iva/static/iva/img/up.png new file mode 100644 index 0000000..36d516c Binary files /dev/null and b/frontend/iva/static/iva/img/up.png differ diff --git a/frontend/iva/static/iva/js/alerts.js b/frontend/iva/static/iva/js/alerts.js new file mode 100644 index 0000000..a9ff0cf --- /dev/null +++ b/frontend/iva/static/iva/js/alerts.js @@ -0,0 +1,57 @@ +/** + * Created by LABS on 30.08.2016. + */ + +$('document').ready(function(){ + + $('#alerts').jplist({ + itemsBox: '.list' + ,itemPath: '.list-item' + ,panelPath: '.jplist-panel' + }); + +}); + +function send_ajax_post_request(cve_id, software_id, option) { + $.ajax({ + type: "POST", + url: "alerts.html", + data: { + csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val(), + cve_id: cve_id, + software_id: software_id, + option: option + }, + success: function (result) { location.reload(); } + }); +} +function set_cve_as_negative(cve_id, software_id) { + send_ajax_post_request(cve_id, software_id, 'set_cve_as_negative'); +} + + +function notify(cve_id, sw_id, sw) { + show_element('sending'); + hide_element('sent_email_failed'); + hide_element('sent_email_ok'); + $('#sending').html('
sending alert for ' + sw + '
'); + $.ajax({ + type: "POST", + url: "alerts.html", + data: { + csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val(), + software_id: sw_id, + option: 'notify' + }, + success: function (result) { + if (result == 'failed') { + show_element('sent_email_failed'); + $("#sent_email_failed").html('
failed to sent alert for ' + sw + '
'); + } else { + location.reload(); + } + hide_element('sending') + } + }); +} + diff --git a/frontend/iva/static/iva/js/change_user_pwd.js b/frontend/iva/static/iva/js/change_user_pwd.js new file mode 100644 index 0000000..11fd089 --- /dev/null +++ b/frontend/iva/static/iva/js/change_user_pwd.js @@ -0,0 +1,64 @@ +function check_old_password() { + $.ajax({ + type: "POST", + url: "change_user_pwd.html", + data: { + username: $("[name='username']").val(), + old_pwd: $("[name='old_pwd']").val(), + csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val(), + verify_old_pwd: true + }, + success: function(is_pwd_correct) { + if (is_pwd_correct == 'True') { + border_green("old_pwd"); + hide_element("wrong_pwd"); + } else { + border_red("old_pwd"); + show_element("wrong_pwd"); + } + enable_change_pwd_button(); + } + }); +} + +function check_new_password() { + if (!is_empty("new_pwd", "empty_pwd")) { + check_repeat() + } + enable_change_pwd_button(); +} + +function check_repeat() { + var pwd_input = document.getElementById("new_pwd"); + var repeat_input = document.getElementById("repeat"); + if (pwd_input.value != repeat_input.value) { + border_red("repeat"); + show_element("repeat_not_equal"); + } else { + border_green("repeat"); + hide_element("repeat_not_equal"); + } + enable_change_pwd_button(); +} + +function is_empty(input_element_id, empty_msg_element_id) { + var input_element = document.getElementById(input_element_id); + if (input_element.value == '') { + border_red(input_element_id); + show_element(empty_msg_element_id); + return true; + } + border_green(input_element_id); + hide_element(empty_msg_element_id); + return false; +} + +function enable_change_pwd_button() { + if (is_element_border_green("old_pwd") && is_element_border_green("new_pwd") && is_element_border_green("repeat")) { + show_element("change_pwd"); + $("#change_pwd").prop('disabled', false) + } else { + hide_element("change_pwd"); + $("#change_pwd").prop('disabled', true) + } +} \ No newline at end of file diff --git a/frontend/iva/static/iva/js/cpe_form.js b/frontend/iva/static/iva/js/cpe_form.js new file mode 100644 index 0000000..9a6d438 --- /dev/null +++ b/frontend/iva/static/iva/js/cpe_form.js @@ -0,0 +1,72 @@ +/** + * Created by LABS on 03.08.2016. + */ + +$('document').ready(function(){ + verify_if_cpe_assigned_already(); + $('#cpe_matches').jplist({ + itemsBox: '.list' + ,itemPath: '.list-item' + ,panelPath: '.jplist-panel' + }); + + var previous_selected_cpe = $(this); + $("body").on('click', '.cpe', function () { + var selected_cpe = $(this); + var cpe_str = selected_cpe.attr('value').replace(/'/g, '"'); + var cpe_json = JSON.parse(cpe_str); + if (selected_cpe.css('color') == 'rgb(0, 0, 0)') { + selected_cpe.css('color','green'); + previous_selected_cpe.css('color', 'black'); + previous_selected_cpe = selected_cpe; + $("[name='vendor']").val(cpe_json.vendor); + $("[name='product']").val(cpe_json.product); + $("[name='version']").val(cpe_json.version); + $("[name='update']").val(cpe_json.update); + $("[name='edition']").val(cpe_json.edition); + $("[name='sw_edition']").val(cpe_json.sw_edition); + $("[name='target_sw']").val(cpe_json.target_sw); + $("[name='target_hw']").val(cpe_json.target_hw); + $("[name='other']").val(cpe_json.other); + $("[name='language']").val(cpe_json.language); + verify_if_cpe_assigned_already(); + } + }); + +}); + +function verify_if_cpe_assigned_already() { + $.ajax({ + type: "POST", + url: "assign_cpe.html", + data: { + csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val(), + part: $("[name='part']").val(), + vendor: $("[name='vendor']").val(), + product: $("[name='product']").val(), + version: $("[name='version']").val(), + update: $("[name='update']").val(), + edition: $("[name='edition']").val(), + sw_edition: $("[name='sw_edition']").val(), + target_sw: $("[name='target_sw']").val(), + target_hw: $("[name='target_hw']").val(), + other: $("[name='other']").val(), + language: $("[name='language']").val(), + check: true + }, + success: function(result) { + var uri_element = document.getElementById('uri_binding'); + var check_element = document.getElementById('check'); + $("#uri_binding").html(result.uri_binding); + if (result.result == 'exist') { + $("#check").html('
CPE was already assigned to other software product
'); + check_element.style.visibility = "visible"; + uri_element.style.color = "red" + } + else { + uri_element.style.color = "#5e5e5e"; + check_element.style.visibility = "hidden"; + } + } + }); +} diff --git a/frontend/iva/static/iva/js/cpes.js b/frontend/iva/static/iva/js/cpes.js new file mode 100644 index 0000000..692421f --- /dev/null +++ b/frontend/iva/static/iva/js/cpes.js @@ -0,0 +1,13 @@ +/** + * Created by LABS on 30.08.2016. + */ + +$('document').ready(function(){ + + $('#cpes').jplist({ + itemsBox: '.list' + ,itemPath: '.list-item' + ,panelPath: '.jplist-panel' + }); + +}); diff --git a/frontend/iva/static/iva/js/cve_matches.js b/frontend/iva/static/iva/js/cve_matches.js new file mode 100644 index 0000000..1ccc2ee --- /dev/null +++ b/frontend/iva/static/iva/js/cve_matches.js @@ -0,0 +1,169 @@ +/** + * Created by LABS on 12.08.2016. + */ + +$('document').ready(function(){ + + $('#cve_matches').jplist({ + itemsBox: '.list' + ,itemPath: '.list-item' + ,panelPath: '.jplist-panel' + }); + + $('#confirmed_cve_matches').jplist({ + itemsBox: '.list' + ,itemPath: '.list-item' + ,panelPath: '.jplist-panel' + }); + + $('#removed_cve_matches').jplist({ + itemsBox: '.list' + ,itemPath: '.list-item' + ,panelPath: '.jplist-panel' + }); + + $("body").on('click', '.show_cpes', function () { + var cve_id = $(this).attr('value'); + var cpes = $('#'+cve_id); + var img_show = $('#show_'+cve_id); + var img_hide = $('#hide_'+cve_id); + cpes.css('visibility', 'visible'); + cpes.css('width', '100%'); + cpes.css('height', '100%'); + img_hide.css('visibility', 'visible'); + img_hide.css('width', '100%'); + img_hide.css('height', '100%'); + img_show.css('visibility', 'hidden'); + img_show.css('width', '0px'); + img_show.css('height', '0px'); + + }); + + $("body").on('click', '.hide_cpes', function () { + var cve_id = $(this).attr('value'); + var cpes = $('#'+cve_id); + var img_show = $('#show_'+cve_id); + var img_hide = $('#hide_'+cve_id); + cpes.css('visibility', 'hidden'); + cpes.css('width', '0px'); + cpes.css('height', '0px'); + img_show.css('visibility', 'visible'); + img_show.css('width', '100%'); + img_show.css('height', '100%'); + img_hide.css('visibility', 'hidden'); + img_hide.css('width', '0px'); + img_hide.css('height', '0px'); + }); + + var select_element = document.getElementById('show_removed'); + if (location.href.indexOf('?option=show_removed') != -1 || + location.href.indexOf('?option=ordered_by_year_desc_show_removed') != -1 || + location.href.indexOf('?option=ordered_by_year_asc_show_removed') != -1) { + select_element.checked = true + } else if (location.href.indexOf('?option=hide_removed') != -1 || + location.href.indexOf('?option=ordered_by_year_desc_hide_removed') != -1 || + location.href.indexOf('?option=ordered_by_year_asc_hide_removed') != -1) { + select_element.checked = false + } + +}); + + +function show_vendor_cves() { + var vendor = document.getElementById('vendor').value; + document.location.href = '?option=show_vendor_cves&vendor='+vendor+'&product=all' +} + + +function show_vendor_product_cves() { + var vendor = document.getElementById('vendor').value; + var product = encodeURIComponent(document.getElementById('product').value); + document.location.href = '?option=show_vendor_cves&vendor='+vendor+'&product='+product; +} + +function show_removed() { + var select_element = document.getElementById('show_removed'); + var vendor = document.getElementById('vendor').value; + var product = encodeURIComponent(document.getElementById('product').value); + if (select_element.checked) { + if (location.href.indexOf('ordered_by_year_desc') != -1) { + document.location.href = '?option=ordered_by_year_desc_show_removed&vendor='+vendor+'&product='+product; + } else if (location.href.indexOf('ordered_by_year_asc') != -1) { + document.location.href = '?option=ordered_by_year_asc_show_removed&vendor='+vendor+'&product='+product; + } else { + document.location.href = '?option=show_removed&vendor='+vendor+'&product='+product; + } + } else { + if (location.href.indexOf('ordered_by_year_desc') != -1) { + document.location.href = '?option=ordered_by_year_desc_hide_removed&vendor='+vendor+'&product='+product; + } else if (location.href.indexOf('ordered_by_year_asc') != -1) { + document.location.href = '?option=ordered_by_year_asc_hide_removed&vendor='+vendor+'&product='+product; + } else { + document.location.href = '?option=hide_removed&vendor='+vendor+'&product='+product; + } + } +} + +function oder_by_year_desc() { + var select_element = document.getElementById('show_removed'); + var vendor = document.getElementById('vendor').value; + var product = encodeURIComponent(document.getElementById('product').value); + if (select_element.checked) { + document.location.href = '?option=ordered_by_year_desc_show_removed&vendor='+vendor+'&product='+product; + } else { + document.location.href = '?option=ordered_by_year_desc_hide_removed&vendor='+vendor+'&product='+product; + } +} + + +function oder_by_year_asc() { + var select_element = document.getElementById('show_removed'); + var vendor = document.getElementById('vendor').value; + var product = encodeURIComponent(document.getElementById('product').value); + if (select_element.checked) { + document.location.href = '?option=ordered_by_year_asc_show_removed&vendor='+vendor+'&product='+product; + } else { + document.location.href = '?option=ordered_by_year_asc_hide_removed&vendor='+vendor+'&product='+product; + } +} + +function modify_cve_match(cve_id, software_id, option) { + $.ajax({ + type: "POST", + url: "cve_matches.html", + data: { + csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val(), + cve_id: cve_id, + software_id: software_id, + option: option + }, + success: function(result) { + location.reload(); + } + }); +} + +function modify_cve_matches_group(cve_id_master, software_id, option) { + $.ajax({ + type: "POST", + url: "grouped_cve_matches.html", + data: { + csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val(), + cve_id_master: cve_id_master, + software_id: software_id, + option: option + }, + beforeSend: function() { + $("#matches_container").hide(); + $("#matches_container_title").hide(); + $("#loading").html('
request is being processed, please wait
'); + }, + success: function(result) { location.reload(); } + }); +} + + + + + + diff --git a/frontend/iva/static/iva/js/daily_db_update.js b/frontend/iva/static/iva/js/daily_db_update.js new file mode 100644 index 0000000..f22c3e3 --- /dev/null +++ b/frontend/iva/static/iva/js/daily_db_update.js @@ -0,0 +1,50 @@ +/** + * Created by LABS on 19.01.2017. + */ + + +function update_task_status(task_name, option) { + $.ajax({ + type: "POST", + url: "local_repositories.html", + data: { + csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val(), + name: task_name, + option: option + }, + success: function(result) { location.reload(); } + }); +} + + +function populate_db() { + $.ajax({ + type: "POST", + url: "local_repositories.html", + data: { + csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val(), + name: 'populate_db', + option: 'populate_db' + }, + success: function(result) { location.reload(); } + }); +} + + +function repopulate_db() { + var is_confirmed = confirm("are you sure you want to repopulate the database? This action may take a couple of hours."); + if (is_confirmed == true) { + $.ajax({ + type: "POST", + url: "local_repositories.html", + data: { + csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val(), + name: 'repopulate_db', + option: 'repopulate_db' + }, + success: function (result) { + location.reload(); + } + }); + } +} \ No newline at end of file diff --git a/frontend/iva/static/iva/js/inventory.js b/frontend/iva/static/iva/js/inventory.js new file mode 100644 index 0000000..7ea7299 --- /dev/null +++ b/frontend/iva/static/iva/js/inventory.js @@ -0,0 +1,14 @@ +/** + * Created by LABS on 30.08.2016. + */ + +$('document').ready(function(){ + + $('#inventory_items').jplist({ + itemsBox: '.list' + ,itemPath: '.list-item' + ,panelPath: '.jplist-panel' + }); + +}); + diff --git a/frontend/iva/static/iva/js/jplist.core.min.js b/frontend/iva/static/iva/js/jplist.core.min.js new file mode 100644 index 0000000..d533ca2 --- /dev/null +++ b/frontend/iva/static/iva/js/jplist.core.min.js @@ -0,0 +1,60 @@ +/** +* jPList - jQuery Data Grid Controls 5.2.0.267 - http://jplist.com +* Copyright 2016 Miriam Zusin +*/ +(function(){var d=function(a,b){var e,c;if(a&&a.controller&&a.controller.collection){e=a.controller.collection.dataitems.length;jQuery.isNumeric(b.index)&&0<=b.index&&b.index<=a.controller.collection.dataitems.length&&(e=Number(b.index));b.$item&&a.controller.collection.addDataItem(b.$item,a.controller.collection.paths,e);if(b.$items)if(c=b.$items,jQuery.isArray(c))for(var d=c.length-1;0<=d;d--)a.controller.collection.addDataItem(c[d],a.controller.collection.paths,e);else c=c.find(a.options.itemPath).addBack(a.options.itemPath), +jQuery(c.get().reverse()).each(function(){a.controller.collection.addDataItem(jQuery(this),a.controller.collection.paths,e)});a.observer.trigger(a.observer.events.unknownStatusesChanged,[!1])}},b=function(a,b){var e;a&&a.controller&&a.controller.collection&&(b.$item&&(a.controller.collection.delDataitem(b.$item),b.$item.remove()),b.$items&&(e=b.$items,jQuery.isArray(b.$items)&&(e=jQuery(b.$items).map(function(){return this.toArray()})),a.controller.collection.delDataitems(e),e.remove()),a.observer.trigger(a.observer.events.unknownStatusesChanged, +[!1]))},c=function(a,e,c){switch(e){case "add":d(a,c);break;case "del":b(a,c);break;case "getDataItems":e=null;if(a.options&&a.options.dataSource)switch(a.options.dataSource.type){case "html":a.controller&&a.controller.collection&&(e=a.controller.collection.dataitems);break;case "server":a.controller&&a.controller.model&&a.controller.model.dataItem&&(e=a.controller.model.dataItem)}return e;case "addControl":a&&a.panel&&a.controller&&(a.panel.addControl(c.$control),a.controller.addPaths(a.panel.paths))}}, +a=function(a){a.observer.on(a.observer.events.knownStatusesChanged,function(b,e){var c;if(e&&(a.history.addStatuses(e),(c=a.panel.mergeStatuses(e))&&0b.options.historyLength&&b.statusesQueue.shift()};jQuery.fn.jplist.History=function(b,c,a){this.options=c;this.observer=a;this.$root=b;this.statusesQueue=[];this.listStatusesQueue=[]};jQuery.fn.jplist.History.prototype.addStatus=function(b){d(this,b)};jQuery.fn.jplist.History.prototype.addStatuses=function(b){for(var c=0;c +this.options.historyLength&&this.listStatusesQueue.shift()};jQuery.fn.jplist.History.prototype.popList=function(){var b=null;0b-1&&(d=0);this.currentPage=d;this.start=this.currentPage*this.itemsOnPage;d=this.itemsNumber;b=this.start+this.itemsOnPage;b>d&&(b=d);this.end=b;d=this.currentPage;this.prevPage=0>=d?0:d-1;d=this.currentPage;b= +this.pagesNumber;this.nextPage=0===b?0:d>=b-1?b-1:d+1}})();(function(){jQuery.fn.jplist.PaginationGoogleService=function(d,b,c){c=Number(c);isNaN(c)&&(c=0);c=this.itemsNumber=c;jQuery.isNumeric(b)?(b=Number(b),isNaN(b)&&(b=c)):b=c;this.itemsOnPage=b;b=(b=this.itemsOnPage)?Math.ceil(this.itemsNumber/b):0;b=this.pagesNumber=b;d=Number(d);isNaN(d)&&(d=0);d>b-1&&(d=0);this.currentPage=d;this.start=this.currentPage*this.itemsOnPage;d=this.itemsNumber;b=this.start+this.itemsOnPage;b>d&&(b=d);this.end=b;d=this.currentPage;this.prevPage=0>=d?0:d-1;d=this.currentPage; +b=this.pagesNumber;this.nextPage=0===b?0:d>=b-1?b-1:d+1}})();(function(){jQuery.fn.jplist.FiltersService.dateFilter=function(d,b,c,a,e,h){for(var f,g,k=[],l=0;l=n&&p<=q&&l.push(m)); +return l}})();(function(){jQuery.fn.jplist.FiltersService.pagerFilter=function(d,b){return b.slice(d.start,d.end)}})();(function(){jQuery.fn.jplist.FiltersService.pathFilter=function(d,b){for(var c,a,e=[],h=0;h=d.length)return b;for(e=0;e=e&&f<=h&&c.push(a):g&&!k?f>=e&&c.push(a):!g&&k&&f<=h&&c.push(a));return c}})();(function(){var d=function(a){var c=[],b;if(a)for(var d=0;d=d.length)return e;k=new jQuery.fn.jplist.PathModel(c,null);for(var n=0;n=a.length)return c;for(e=0;ee?1:-1:ad?1:-1:e"),{});d=new jQuery.fn.jplist.Dataitems(c,d,a);d.sort([b]);return"html"===f?d.dataviewToString():d.dataviewToJqueryObject()}})();(function(){var d=function(c,a){var b=null;if("{month}"==c){a=a.toLowerCase();if("january"===a||"jan"===a||"jan."===a)b=0;if("february"===a||"feb"===a||"feb."===a)b=1;if("march"===a||"mar"===a||"mar."===a)b=2;if("april"==a||"apr"===a||"apr."===a)b=3;"may"===a&&(b=4);if("july"==a||"jun"===a||"jun."===a)b=5;if("april"===a||"jul"===a||"jul."===a)b=6;if("august"===a||"aug"===a||"aug."===a)b=7;if("september"===a||"sep"===a||"sep."===a)b=8;if("october"===a||"oct"===a||"oct."===a)b=9;if("november"===a|| +"nov"===a||"nov."===a)b=10;if("december"===a||"dec"===a||"dec."===a)b=11;null===b&&(b=parseInt(a,10),isNaN(b)||b--)}else b=parseInt(a,10);return b},b=function(b,a){var e,d=null;e=b.replace(/{year}|{month}|{day}|{hour}|{min}|{sec}/g,".*");(e=(new RegExp(e,"g")).exec(a))&&1"+d;a+=""}return a};jQuery.fn.jplist.HelperService.removeCharacters=function(b,a){return b?b.replace(new RegExp(a,"ig"),"").toLowerCase():""};jQuery.fn.jplist.HelperService.formatDateTime=function(c,a){var e,h,f,g,k,l;a=a.replace(/\./g,"\\.");a=a.replace(/\(/g,"\\(");a=a.replace(/\)/g,"\\)");a=a.replace(/\[/g,"\\[");a=a.replace(/\]/g,"\\]");e=a.replace("{year}","(.*)");(h=b(e,c))&&(h=d("{year}", +h));e=a.replace("{day}","(.*)");(g=b(e,c))&&(g=d("{day}",g));e=a.replace("{month}","(.*)");(f=b(e,c))&&(f=d("{month}",f));e=a.replace("{hour}","(.*)");(k=b(e,c))&&(k=d("{hour}",k));e=a.replace("{min}","(.*)");(l=b(e,c))&&(l=d("{min}",l));e=a.replace("{sec}","(.*)");(e=b(e,c))&&(e=d("{sec}",e));if(!h||isNaN(h))h=1900;if(!f||isNaN(f))f=0;if(!g||isNaN(g))g=1;if(!k||isNaN(k))k=0;if(!l||isNaN(l))l=0;if(!e||isNaN(e))e=0;return new Date(h,f,g,k,l,e)}})();(function(){jQuery.fn.jplist.DeepLinksService={};jQuery.fn.jplist.DeepLinksService.getUrlParams=function(d){var b=[],c,a=[],e,b=window.decodeURIComponent(jQuery.trim(window.location.hash.replace(d.hashStart,"")));if(d.deepLinking&&""!==jQuery.trim(b))for(var b=b.split(d.delimiter1),h=0;h=d.length||0>=f.length?(b.$noResults.removeClass("jplist-hidden"),b.$itemsBox.addClass("jplist-hidden"),jQuery.isFunction(b.redrawCallback)&&b.redrawCallback(c,f,a)):(b.$noResults.addClass("jplist-hidden"),b.$itemsBox.removeClass("jplist-hidden"),b.effect&&jQuery.fn.jplist.animation?(e&&!e.inAnimation&&(g=!0),e=g?k:b.options,jQuery.fn.jplist.animation.drawItems(e, +b.$itemsBox,d,f,b.effect,function(){jQuery.isFunction(b.redrawCallback)&&b.redrawCallback(c,f,a)},b.observer)):(d.detach(),b.$itemsBox.append(f),jQuery.isFunction(b.redrawCallback)&&b.redrawCallback(c,f,a)));return f};jQuery.fn.jplist.DOMView=function(b,c,a,e,d,f,g){this.options=c;this.$root=b;this.observer=a;this.redrawCallback=f;this.effect=g;this.$itemsBox=b.find(e).eq(0);this.$noResults=b.find(d)};jQuery.fn.jplist.DOMView.prototype.render=function(b,c,a){return d(this,b,c,a)}})();(function(){jQuery.fn.jplist.DOMController=function(d,b,c,a){this.observer=c;this.$root=d;this.listView=new jQuery.fn.jplist.DOMView(d,b,c,b.itemsBox,b.noResults,b.redrawCallback,b.effect);d=b.itemPath;b=this.$root.find(b.itemsBox).eq(0).find(d);this.collection=new jQuery.fn.jplist.Dataitems(this.observer,b,a)};jQuery.fn.jplist.DOMController.prototype.renderStatuses=function(d,b){var c;this.collection&&(this.collection.applyStatuses(d),c=this.listView.render(this.collection,d,b));return c};jQuery.fn.jplist.DOMController.prototype.addPaths= +function(d){this.collection.addPaths(d)}})();(function(){jQuery.fn.jplist.ItemControlFactory=function(d,b,c,a){this.options=d;this.observer=b;this.history=c;this.$root=a};jQuery.fn.jplist.ItemControlFactory.prototype.create=function(d){var b=null,c,a,e=null,h=null;c=d.attr("data-control-type");a={};jQuery.fn.jplist.itemControlTypes[c]&&(a=jQuery.extend(!0,{},a,jQuery.fn.jplist.itemControlTypes[c]));this.options.itemControlTypes&&this.options.itemControlTypes[c]&&(a=jQuery.extend(!0,{},a,this.options.itemControlTypes[c]));a&&(a.className&&(e= +jQuery.fn.jplist.itemControls[a.className]),a.options&&(h=a.options));c={type:c,itemControlType:a,controlTypeClass:e,controlOptions:h};c=jQuery.extend(!0,c,{$control:d,history:this.history,observer:this.observer,options:this.options,$root:this.$root});c.controlTypeClass&&jQuery.isFunction(c.controlTypeClass)&&(b=new c.controlTypeClass(c));return b}})();(function(){var d=function(b,c){c&&0c?a.addClass("jplist-sticky"):a.removeClass("jplist-sticky"))},b=function(a,b){b.each(function(){var a=jQuery(this),b=a.offset().top;a.data("top",b);d(a)});jQuery(window).scroll(function(){b.each(function(){d(jQuery(this))})})},c=function(b){var c=[];if("cookies"===b.options.storage||"localstorage"===b.options.storage&&jQuery.fn.jplist.LocalStorageService.supported())if("cookies"===b.options.storage&& +(c=jQuery.fn.jplist.CookiesService.restoreCookies(b.options.storageName)),"localstorage"===b.options.storage&&jQuery.fn.jplist.LocalStorageService.supported()&&(c=jQuery.fn.jplist.LocalStorageService.restore(b.options.storageName)),0=a.length?c(this):this.controls.setDeepLinks(a,this.observer)};jQuery.fn.jplist.PanelController.prototype.setStatusesFromStorage=function(){c(this)};jQuery.fn.jplist.PanelController.prototype.setStatuses=function(a){this.controls.setStatuses(a, +!1);this.history.addList(a)};jQuery.fn.jplist.PanelController.prototype.getStatuses=function(a){return this.controls.getStatuses(a)};jQuery.fn.jplist.PanelController.prototype.mergeStatuses=function(a){return this.controls.merge(!1,a)};jQuery.fn.jplist.PanelController.prototype.statusesChangedByDeepLinks=function(a,b){this.controls&&this.controls.statusesChangedByDeepLinks(b)};jQuery.fn.jplist.PanelController.prototype.getDeepLinksURLPerControls=function(){return this.controls.getDeepLinksUrl(this.options.delimiter1)}})();(function(){jQuery.fn.jplist.ControlFactory=function(d,b,c,a){this.options=d;this.observer=b;this.history=c;this.$root=a};jQuery.fn.jplist.ControlFactory.prototype.create=function(d,b){var c=null,a,e,h,f,g,k,l,m;a=d.attr("data-control-type");g=f=h=!0;k=!1;m=l=null;(e=d.attr("data-control-deep-link"))&&"false"===e.toString()&&(h=!1);(e=d.attr("data-control-storage"))&&"false"===e.toString()&&(f=!1);(e=d.attr("data-control-animation"))&&"false"===e.toString()&&(g=!1);(e=d.attr("data-control-animate-to-top"))&& +"true"===e.toString()&&(k=!0);e={};jQuery.fn.jplist.controlTypes[a]&&(e=jQuery.extend(!0,{},e,jQuery.fn.jplist.controlTypes[a]));this.options.controlTypes&&this.options.controlTypes[a]&&(e=jQuery.extend(!0,{},e,this.options.controlTypes[a]));e&&(e.className&&(l=jQuery.fn.jplist.controls[e.className]),e.options&&(m=e.options));a={type:a,action:d.attr("data-control-action"),name:d.attr("data-control-name"),inDeepLinking:h,inStorage:f,inAnimation:g,isAnimateToTop:k,controlType:e,controlTypeClass:l,controlOptions:m, +paths:[]};a=jQuery.extend(!0,a,{$control:d,history:this.history,observer:this.observer,options:this.options,$root:this.$root,controlsCollection:b});a.controlTypeClass&&jQuery.isFunction(a.controlTypeClass)&&(c=new a.controlTypeClass(a));return c};jQuery.fn.jplist.ControlFactory.prototype.getStatus=function(d){return null};jQuery.fn.jplist.ControlFactory.prototype.setStatus=function(d,b){};jQuery.fn.jplist.ControlFactory.prototype.getDeepLink=function(){return""};jQuery.fn.jplist.ControlFactory.prototype.getStatusByDeepLink= +function(d,b){return null};jQuery.fn.jplist.ControlFactory.prototype.getPaths=function(d){return[]};jQuery.fn.jplist.ControlFactory.prototype.setByDeepLink=function(d){}})();(function(){var d=function(a,b){for(var c=[],d,g=0;g=a.$control.find(".jplist-dd-panel").length&&a.$control.prepend('
'+ +c.text()+"
");b(a);return jQuery.extend(this,a)};jQuery.fn.jplist.DropdownControl=function(a,b,d,f){return new c(a,b,d,f)}})();(function(){jQuery.fn.jplist.Storage=function(d,b,c){this.storageType=d;this.storageName=b;this.cookiesExpiration=c;this.isStorageEnabled="cookies"===d||"localstorage"===d&&jQuery.fn.jplist.LocalStorageService.supported()};jQuery.fn.jplist.Storage.prototype.save=function(d){var b=[],c;if(d&&this.isStorageEnabled){for(var a=0;a=b.pagesNumber?(this.$control.html(""),this.$control.addClass("jplist-empty")):(this.$control.removeClass("jplist-empty"),a=this.$control.attr("data-type"),a=a.replace("{current}",b.currentPage+1),a=a.replace("{pages}",b.pagesNumber),a=a.replace("{start}",b.start+1), +a=a.replace("{end}",b.end),a=a.replace("{all}",b.itemsNumber),this.$control.html(a))};jQuery.fn.jplist.controls.PaginationInfo=function(g){return new f(g)};jQuery.fn.jplist.controlTypes["pagination-info"]={className:"PaginationInfo",options:{}}})();(function(){var f=function(b,a){var e;e=null;var c;c=!1;e=b.$control.find("button[data-active]").eq(0);0>=e.length&&(e=b.$control.find("button").eq(0));e=a?0:Number(e.attr("data-number"))||0;(c="true"===b.$control.attr("data-jump-to-start")||b.controlOptions.jumpToStart)&&(c=b.history.getLastStatus())&&"pagination"!==c.type&&"views"!==c.type&&(e=0);c=Number(b.$control.attr("data-items-per-page"))||0;e=new jQuery.fn.jplist.controls.PaginationDTO(e,c);return e=new jQuery.fn.jplist.StatusDTO(b.name, +b.action,b.type,e,b.inStorage,b.inAnimation,b.isAnimateToTop,b.inDeepLinking)},g=function(b){b.$control.on("click","button",function(){var a,e=null;a=jQuery(this);var c;a=Number(a.attr("data-number"))||0;e=f(b,!1);e.data.currentPage=a;c=b.$root.find('[data-control-type="pagination"]');c.find("button").removeAttr("data-active");c.find('button[data-number="'+a+'"]').each(function(){jQuery(this).attr("data-active",!0)});b.observer.trigger(b.observer.events.knownStatusesChanged,[[e]])})},d=function(b){b.params= +{view:new jQuery.fn.jplist.controls.PaginationView(b.$control,b.controlOptions)};g(b);return jQuery.extend(this,b)};d.prototype.getStatus=function(b){return f(this,b)};d.prototype.getDeepLink=function(){var b="",a;if(this.inDeepLinking&&(a=f(this,!1),a.data))if(jQuery.isNumeric(a.data.currentPage)&&(b=this.name+this.options.delimiter0+"currentPage="+a.data.currentPage),this.$control.attr("data-items-per-page"))b&&(b+=this.options.delimiter1),b+=this.name+this.options.delimiter0+"number="+a.data.number; +else if(jQuery.isNumeric(a.data.number)||"all"===a.data.number)b=this.name+this.options.delimiter0+"number="+a.data.number;return b};d.prototype.getStatusByDeepLink=function(b,a){var e;a:if(e=null,this.inDeepLinking){if("currentPage"!==b){e=null;break a}e=f(this,!0);e.data&&"currentPage"===b&&(e.data.currentPage=a)}return e};d.prototype.setStatus=function(b,a){var e;if(jQuery.isArray(b))for(var c=0;c ";return e+""},g=function(d,b){var a={$control:d,options:b,$pagingprev:null,$pagingmid:null,$pagingnext:null,$jplistFirst:null,$jplistPrev:null,$jplistNext:null,$jplistLast:null,mode:d.attr("data-mode")},e,c,f,h;e=a.$control.attr("data-prev")|| +a.options.prevArrow;c=a.$control.attr("data-next")||a.options.nextArrow;f=a.$control.attr("data-first")||a.options.firstArrow;h=a.$control.attr("data-last")||a.options.lastArrow;a.$control.html('
');a.$pagingprev=a.$control.find('[data-type="pagingprev"]');a.$pagingmid=a.$control.find('[data-type="pagingmid"]');a.$pagingnext= +a.$control.find('[data-type="pagingnext"]');a.$pagingprev.html('");a.$pagingnext.html('");a.$jplistFirst=a.$pagingprev.find('[data-type="first"]');a.$jplistPrev=a.$pagingprev.find('[data-type="prev"]'); +a.$jplistNext=a.$pagingnext.find('[data-type="next"]');a.$jplistLast=a.$pagingnext.find('[data-type="last"]');return jQuery.extend(this,a)};g.prototype.build=function(d){if(0<=d.currentPage&&d.currentPageb&&(b=0);a=b+a;a>d.pagesNumber&&(a=d.pagesNumber);b=f(b,a,d.currentPage);this.$pagingmid.html(b); +break;default:var e;e=Number(this.$control.attr("data-range"))||this.options.range;a=Math.floor(d.currentPage/e);b=e*(a+1);b>d.pagesNumber&&(b=d.pagesNumber);b=f(e*a,b,d.currentPage);this.$pagingmid.html(b)}this.$jplistPrev.attr("data-number",d.prevPage).removeClass("jplist-current");this.$jplistNext.attr("data-number",d.nextPage).removeClass("jplist-current");this.$jplistLast.attr("data-number",d.pagesNumber-1).removeClass("jplist-current");1>=d.pagesNumber?this.$control.addClass("jplist-one-page"): +this.$control.removeClass("jplist-one-page")}else this.$control.addClass("jplist-hidden");0===d.currentPage?this.$pagingprev.addClass("jplist-hidden"):this.$pagingprev.removeClass("jplist-hidden");d.currentPage==d.pagesNumber-1?this.$pagingnext.addClass("jplist-hidden"):this.$pagingnext.removeClass("jplist-hidden")};jQuery.fn.jplist.controls.PaginationView=function(d,b){return new g(d,b)};jQuery.fn.jplist.controlTypes.pagination={className:"Pagination",options:{range:7,jumpToStart:!1,prevArrow:"‹", +nextArrow:"›",firstArrow:"«",lastArrow:"»"}}})();(function(){jQuery.fn.jplist.controls.PaginationDTO=function(f,g){var d={currentPage:f,paging:null};g&&(d.number=g);return d}})();(function(){var f=function(a,b){var c=null;b?(c=a.$control.find('li:has(span[data-default="true"])').eq(0),0>=c.length&&(c=a.$control.find("li:eq(0)"))):c=a.$control.find(".active");c=c.find("span");c=new jQuery.fn.jplist.controls.DropdownPaginationDTO(c.attr("data-number"));return c=new jQuery.fn.jplist.StatusDTO(a.name,a.action,a.type,c,a.inStorage,a.inAnimation,a.isAnimateToTop,a.inDeepLinking)},g=function(a,b){var c,d,f;a.$control.find("span").each(function(){c=jQuery(this).attr("data-path"); +d=jQuery(this).attr("data-type");c&&""!==jQuery.trim(c)&&(f=new jQuery.fn.jplist.PathModel(c,d),b.push(f))})},d=function(a){a.$control.find("li").off().on("click",function(){var b,c,d,g;b=f(a,!1);g=jQuery(this).find("span");c=g.attr("data-path");d=g.attr("data-number");c?(b.data.path=c,b.data.type=g.attr("data-type"),b.data.order=g.attr("data-order")):d&&(b.data.number=d);a.observer.trigger(a.observer.events.knownStatusesChanged,[[b]])})},b=function(a){new jQuery.fn.jplist.DropdownControl(a.options, +a.observer,a.history,a.$control);d(a);return jQuery.extend(this,a)};b.prototype.getStatus=function(a){return f(this,a)};b.prototype.getDeepLink=function(){var a="",b;this.inDeepLinking&&(b=f(this,!1),b.data&&(jQuery.isNumeric(b.data.number)||"all"===b.data.number)&&(a=this.name+this.options.delimiter0+"number="+b.data.number));return a};b.prototype.getStatusByDeepLink=function(a,b){var c;a:if(c=null,this.inDeepLinking){if("number"!==a&&a!=="path"+this.options.delimiter2+"type"+this.options.delimiter2+ +"order"&&"path"!==a){c=null;break a}c=f(this,!0);c.data&&"number"===a&&jQuery.isNumeric(c.data.number)&&(c.data.number=b)}return c};b.prototype.getPaths=function(a){g(this,a)};b.prototype.setStatus=function(a,b){var c,d;if(jQuery.isArray(a))for(d=0;d=c.length&&(c=d.eq(0)),c.addClass("active"),this.$control.find(".jplist-dd-panel").text(c.eq(0).text()))};jQuery.fn.jplist.controls.ItemsPerPageDropdown=function(a){return new b(a)};jQuery.fn.jplist.controlTypes["items-per-page-drop-down"]={className:"ItemsPerPageDropdown",options:{},dropdown:!0}})();(function(){var f=function(a,b){var c;c=null;b?(c=a.$control.find('option[data-default="true"]').eq(0),0>=c.length&&(c=a.$control.find("option").eq(0))):c=a.$control.find("option:selected");c=new jQuery.fn.jplist.controls.DropdownPaginationDTO(c.attr("data-number"));return c=new jQuery.fn.jplist.StatusDTO(a.name,a.action,a.type,c,a.inStorage,a.inAnimation,a.isAnimateToTop,a.inDeepLinking)},g=function(a,b){var c,d,f;a.$control.find("option").each(function(){c=jQuery(this).attr("data-path");d=jQuery(this).attr("data-type"); +c&&(f=new jQuery.fn.jplist.PathModel(c,d),b.push(f))})},d=function(a){a.$control.change(function(){var b,c,d;b=f(a,!1);c=jQuery(this).find("option:selected");d=c.attr("data-path");c=c.attr("data-number");d?(b.data.path=d,b.data.type=jQuery(this).attr("data-type"),b.data.order=jQuery(this).attr("data-order")):c&&(b.data.number=c);a.observer.trigger(a.observer.events.knownStatusesChanged,[[b]])})},b=function(a){d(a);return jQuery.extend(this,a)};b.prototype.getStatus=function(a){return f(this,a)};b.prototype.getDeepLink= +function(){var a="",b;this.inDeepLinking&&(b=f(this,!1),b.data&&(jQuery.isNumeric(b.data.number)||"all"===b.data.number)&&(a=this.name+this.options.delimiter0+"number="+b.data.number));return a};b.prototype.getStatusByDeepLink=function(a,b){var c=null;this.inDeepLinking&&(c=f(this,!0),c.data&&"number"===a&&jQuery.isNumeric(c.data.number)&&(c.data.number=b));return c};b.prototype.getPaths=function(a){g(this,a)};b.prototype.setStatus=function(a,b){var c;if(jQuery.isArray(a))for(var d=0;d=b.length&&(b=a.$control.find("option").eq(0))):b=a.$control.find("option:selected");d=a.$control.attr("data-datetime-format")||"";e=a.$control.attr("data-ignore")||"";d=new jQuery.fn.jplist.controls.DropdownSortDTO(b.attr("data-path"),b.attr("data-type"), +b.attr("data-order"),d,e);return b=new jQuery.fn.jplist.StatusDTO(a.name,a.action,a.type,d,a.inStorage,a.inAnimation,a.isAnimateToTop,a.inDeepLinking,f(b))},k=function(a,c){var b,d,e;a.$control.find("option").each(function(){b=jQuery(this).attr("data-path");d=jQuery(this).attr("data-type");b&&(e=new jQuery.fn.jplist.PathModel(b,d),c.push(e))})},h=function(a){a.$control.on("change",function(){var c,b,d;c=e(a,!1);b=jQuery(this).find("option:selected");if(d=b.attr("data-path"))c.data.path=d,c.data.type= +b.attr("data-type"),c.data.order=b.attr("data-order"),c.data.additionalPaths=f(b);a.observer.trigger(a.observer.events.knownStatusesChanged,[[c]])})},g=function(a){h(a);return jQuery.extend(this,a)};g.prototype.getStatus=function(a){return e(this,a)};g.prototype.getDeepLink=function(){var a="",c;this.inDeepLinking&&(c=e(this,!1),c.data&&c.data.path&&c.data.type&&c.data.order&&(a=this.name+this.options.delimiter0+"path"+this.options.delimiter2+"type"+this.options.delimiter2+"order="+c.data.path+this.options.delimiter2+ +c.data.type+this.options.delimiter2+c.data.order));return a};g.prototype.getStatusByDeepLink=function(a,c){var b=null,d;this.inDeepLinking&&(b=e(this,!0),b.data&&a==="path"+this.options.delimiter2+"type"+this.options.delimiter2+"order"&&(d=c.split(this.options.delimiter2),3===d.length&&(b.data.path=d[0],b.data.type=d[1],b.data.order=d[2])));return b};g.prototype.getPaths=function(a){k(this,a)};g.prototype.setStatus=function(a,c){var b;b="default"==a.data.path?this.$control.find('option[data-path="'+ +a.data.path+'"]'):this.$control.find('option[data-path="'+a.data.path+'"][data-type="'+a.data.type+'"][data-order="'+a.data.order+'"]');0=b.length&&(b=a.$control.find("li:eq(0)"))):b=a.$control.find(".active");b=b.find("span");d=a.$control.attr("data-datetime-format")||"";e=a.$control.attr("data-ignore")||"";b=new jQuery.fn.jplist.controls.DropdownSortDTO(b.attr("data-path"),b.attr("data-type"), +b.attr("data-order"),d,e,f(b));return b=new jQuery.fn.jplist.StatusDTO(a.name,a.action,a.type,b,a.inStorage,a.inAnimation,a.isAnimateToTop,a.inDeepLinking)},k=function(a,c){a.$control.find("span").each(function(){var a,d;a=jQuery(this).attr("data-path");d=jQuery(this).attr("data-type");a&&""!==jQuery.trim(a)&&(a=new jQuery.fn.jplist.PathModel(a,d),c.push(a))})},h=function(a){a.$control.find("li").off("click").on("click",function(){var c,b,d;c=e(a,!1);d=jQuery(this).find("span");if(b=d.attr("data-path"))c.data.path= +b,c.data.type=d.attr("data-type"),c.data.order=d.attr("data-order"),c.data.additionalPaths=f(d);a.observer.trigger(a.observer.events.knownStatusesChanged,[[c]])})},g=function(a){new jQuery.fn.jplist.DropdownControl(a.options,a.observer,a.history,a.$control);h(a);return jQuery.extend(this,a)};g.prototype.getStatus=function(a){return e(this,a)};g.prototype.getDeepLink=function(){var a="",c;this.inDeepLinking&&(c=e(this,!1),c.data&&c.data.path&&c.data.type&&c.data.order&&(a=this.name+this.options.delimiter0+ +"path"+this.options.delimiter2+"type"+this.options.delimiter2+"order="+c.data.path+this.options.delimiter2+c.data.type+this.options.delimiter2+c.data.order));return a};g.prototype.getStatusByDeepLink=function(a,c){var b;a:{b=null;var d;if(this.inDeepLinking){if("number"!==a&&a!=="path"+this.options.delimiter2+"type"+this.options.delimiter2+"order"&&"path"!==a){b=null;break a}b=e(this,!0);b.data&&a==="path"+this.options.delimiter2+"type"+this.options.delimiter2+"order"&&(d=c.split(this.options.delimiter2), +3===d.length&&(b.data.path=d[0],b.data.type=d[1],b.data.order=d[2]))}}return b};g.prototype.getPaths=function(a){k(this,a)};g.prototype.setStatus=function(a,c){var b,d;d=this.$control.find("li");d.removeClass("active");b="default"==a.data.path?this.$control.find('li:has([data-path="default"])'):this.$control.find('li:has([data-path="'+a.data.path+'"][data-type="'+a.data.type+'"][data-order="'+a.data.order+'"])');0>=b.length&&(b=d.eq(0));b.addClass("active");this.$control.find(".jplist-dd-panel").text(b.eq(0).text())}; +jQuery.fn.jplist.controls.SortDropdown=function(a){return new g(a)};jQuery.fn.jplist.controlTypes["sort-drop-down"]={className:"SortDropdown",options:{},dropdown:!0}})();(function(){jQuery.fn.jplist.controls.DropdownSortDTO=function(f,e,k,h,g,a){return{path:f,type:e,order:k,dateTimeFormat:h,ignore:g,additionalPaths:a}}})(); diff --git a/frontend/iva/static/iva/js/jplist.textbox-filter.min.js b/frontend/iva/static/iva/js/jplist.textbox-filter.min.js new file mode 100644 index 0000000..6093f44 --- /dev/null +++ b/frontend/iva/static/iva/js/jplist.textbox-filter.min.js @@ -0,0 +1,8 @@ +/** +* jPList - jQuery Data Grid Controls 5.2.0.9 - http://jplist.com +* Copyright 2016 Miriam Zusin +*/ +(function(){var d=function(a,b){var e,c;e=a.$control.attr("data-path");c=b?a.$control.attr("value")||"":a.$control.val();e=new jQuery.fn.jplist.controls.TextboxDTO(e,c,a.params.ignore,a.params.mode);return new jQuery.fn.jplist.StatusDTO(a.name,a.action,a.type,e,a.inStorage,a.inAnimation,a.isAnimateToTop,a.inDeepLinking)},f=function(a){if(a.params.$button&&0=0&&c0&&b-1 in a)}var x=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=function(a,b){for(var c=0,d=a.length;c+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(N),U=new RegExp("^"+L+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L+"|[*])"),ATTR:new RegExp("^"+M),PSEUDO:new RegExp("^"+N),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),aa=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:d<0?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ba=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g,ca=function(a,b){return b?"\0"===a?"\ufffd":a.slice(0,-1)+"\\"+a.charCodeAt(a.length-1).toString(16)+" ":"\\"+a},da=function(){m()},ea=ta(function(a){return a.disabled===!0},{dir:"parentNode",next:"legend"});try{G.apply(D=H.call(v.childNodes),v.childNodes),D[v.childNodes.length].nodeType}catch(fa){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s=b&&b.ownerDocument,w=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==w&&9!==w&&11!==w)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==w&&(l=Z.exec(a)))if(f=l[1]){if(9===w){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(s&&(j=s.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(l[2])return G.apply(d,b.getElementsByTagName(a)),d;if((f=l[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==w)s=b,r=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(ba,ca):b.setAttribute("id",k=u),o=g(a),h=o.length;while(h--)o[h]="#"+k+" "+sa(o[h]);r=o.join(","),s=$.test(a)&&qa(b.parentNode)||b}if(r)try{return G.apply(d,s.querySelectorAll(r)),d}catch(x){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(P,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("fieldset");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&a.sourceIndex-b.sourceIndex;if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return function(b){return"label"in b&&b.disabled===a||"form"in b&&b.disabled===a||"form"in b&&b.disabled===!1&&(b.isDisabled===a||b.isDisabled!==!a&&("label"in b||!ea(b))!==a)}}function pa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function qa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return!!b&&"HTML"!==b.nodeName},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),v!==n&&(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(n.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}},d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){if("undefined"!=typeof b.getElementsByClassName&&p)return b.getElementsByClassName(a)},r=[],q=[],(c.qsa=Y.test(n.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+K+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+K+"*(?:value|"+J+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){a.innerHTML="";var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+K+"*[*^$|!~]?="),2!==a.querySelectorAll(":enabled").length&&q.push(":enabled",":disabled"),o.appendChild(a).disabled=!0,2!==a.querySelectorAll(":disabled").length&&q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Y.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"*"),s.call(a,"[s!='']:x"),r.push("!=",N)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Y.test(o.compareDocumentPosition),t=b||Y.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?I(k,a)-I(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?I(k,a)-I(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?la(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(S,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.escape=function(a){return(a+"").replace(ba,ca)},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(_,aa),a[3]=(a[3]||a[4]||a[5]||"").replace(_,aa),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return V.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&T.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(_,aa).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+K+")"+a+"("+K+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:!b||(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(O," ")+" ").indexOf(c)>-1:"|="===b&&(e===c||e.slice(0,c.length+1)===c+"-"))}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=I(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(P,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(_,aa),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return U.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(_,aa).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:oa(!1),disabled:oa(!0),checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return X.test(a.nodeName)},input:function(a){return W.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:pa(function(){return[0]}),last:pa(function(a,b){return[b-1]}),eq:pa(function(a,b,c){return[c<0?c+b:c]}),even:pa(function(a,b){for(var c=0;c=0;)a.push(d);return a}),gt:pa(function(a,b,c){for(var d=c<0?c+b:c;++d1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function va(a,b,c){for(var d=0,e=b.length;d-1&&(f[j]=!(g[j]=l))}}else r=wa(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ya(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ta(function(a){return a===b},h,!0),l=ta(function(a){return I(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];i1&&ua(m),i>1&&sa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(P,"$1"),c,i0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=E.call(i));u=wa(u)}G.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&ga.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=ya(b[c]),f[u]?d.push(f):e.push(f);f=A(a,za(e,d)),f.selector=a}return f},i=ga.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(_,aa),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=V.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(_,aa),$.test(j[0].type)&&qa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&sa(j),!a)return G.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,!b||$.test(a)&&qa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("fieldset"))}),ja(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){if(!c)return a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){if(!c&&"input"===a.nodeName.toLowerCase())return a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(J,function(a,b,c){var d;if(!c)return a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);r.find=x,r.expr=x.selectors,r.expr[":"]=r.expr.pseudos,r.uniqueSort=r.unique=x.uniqueSort,r.text=x.getText,r.isXMLDoc=x.isXML,r.contains=x.contains,r.escapeSelector=x.escape;var y=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&r(a).is(c))break;d.push(a)}return d},z=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},A=r.expr.match.needsContext,B=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i,C=/^.[^:#\[\.,]*$/;function D(a,b,c){if(r.isFunction(b))return r.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return r.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(C.test(b))return r.filter(b,a,c);b=r.filter(b,a)}return r.grep(a,function(a){return i.call(b,a)>-1!==c&&1===a.nodeType})}r.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?r.find.matchesSelector(d,a)?[d]:[]:r.find.matches(a,r.grep(b,function(a){return 1===a.nodeType}))},r.fn.extend({find:function(a){var b,c,d=this.length,e=this;if("string"!=typeof a)return this.pushStack(r(a).filter(function(){for(b=0;b1?r.uniqueSort(c):c},filter:function(a){return this.pushStack(D(this,a||[],!1))},not:function(a){return this.pushStack(D(this,a||[],!0))},is:function(a){return!!D(this,"string"==typeof a&&A.test(a)?r(a):a||[],!1).length}});var E,F=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,G=r.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||E,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:F.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof r?b[0]:b,r.merge(this,r.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),B.test(e[1])&&r.isPlainObject(b))for(e in b)r.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&(this[0]=f,this.length=1),this}return a.nodeType?(this[0]=a,this.length=1,this):r.isFunction(a)?void 0!==c.ready?c.ready(a):a(r):r.makeArray(a,this)};G.prototype=r.fn,E=r(d);var H=/^(?:parents|prev(?:Until|All))/,I={children:!0,contents:!0,next:!0,prev:!0};r.fn.extend({has:function(a){var b=r(a,this),c=b.length;return this.filter(function(){for(var a=0;a-1:1===c.nodeType&&r.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?r.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?i.call(r(a),this[0]):i.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(r.uniqueSort(r.merge(this.get(),r(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function J(a,b){while((a=a[b])&&1!==a.nodeType);return a}r.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return y(a,"parentNode")},parentsUntil:function(a,b,c){return y(a,"parentNode",c)},next:function(a){return J(a,"nextSibling")},prev:function(a){return J(a,"previousSibling")},nextAll:function(a){return y(a,"nextSibling")},prevAll:function(a){return y(a,"previousSibling")},nextUntil:function(a,b,c){return y(a,"nextSibling",c)},prevUntil:function(a,b,c){return y(a,"previousSibling",c)},siblings:function(a){return z((a.parentNode||{}).firstChild,a)},children:function(a){return z(a.firstChild)},contents:function(a){return a.contentDocument||r.merge([],a.childNodes)}},function(a,b){r.fn[a]=function(c,d){var e=r.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=r.filter(d,e)),this.length>1&&(I[a]||r.uniqueSort(e),H.test(a)&&e.reverse()),this.pushStack(e)}});var K=/\S+/g;function L(a){var b={};return r.each(a.match(K)||[],function(a,c){b[c]=!0}),b}r.Callbacks=function(a){a="string"==typeof a?L(a):r.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h-1)f.splice(c,1),c<=h&&h--}),this},has:function(a){return a?r.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=g=[],c||b||(f=c=""),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j};function M(a){return a}function N(a){throw a}function O(a,b,c){var d;try{a&&r.isFunction(d=a.promise)?d.call(a).done(b).fail(c):a&&r.isFunction(d=a.then)?d.call(a,b,c):b.call(void 0,a)}catch(a){c.call(void 0,a)}}r.extend({Deferred:function(b){var c=[["notify","progress",r.Callbacks("memory"),r.Callbacks("memory"),2],["resolve","done",r.Callbacks("once memory"),r.Callbacks("once memory"),0,"resolved"],["reject","fail",r.Callbacks("once memory"),r.Callbacks("once memory"),1,"rejected"]],d="pending",e={state:function(){return d},always:function(){return f.done(arguments).fail(arguments),this},"catch":function(a){return e.then(null,a)},pipe:function(){var a=arguments;return r.Deferred(function(b){r.each(c,function(c,d){var e=r.isFunction(a[d[4]])&&a[d[4]];f[d[1]](function(){var a=e&&e.apply(this,arguments);a&&r.isFunction(a.promise)?a.promise().progress(b.notify).done(b.resolve).fail(b.reject):b[d[0]+"With"](this,e?[a]:arguments)})}),a=null}).promise()},then:function(b,d,e){var f=0;function g(b,c,d,e){return function(){var h=this,i=arguments,j=function(){var a,j;if(!(b=f&&(d!==N&&(h=void 0,i=[a]),c.rejectWith(h,i))}};b?k():(r.Deferred.getStackHook&&(k.stackTrace=r.Deferred.getStackHook()),a.setTimeout(k))}}return r.Deferred(function(a){c[0][3].add(g(0,a,r.isFunction(e)?e:M,a.notifyWith)),c[1][3].add(g(0,a,r.isFunction(b)?b:M)),c[2][3].add(g(0,a,r.isFunction(d)?d:N))}).promise()},promise:function(a){return null!=a?r.extend(a,e):e}},f={};return r.each(c,function(a,b){var g=b[2],h=b[5];e[b[1]]=g.add,h&&g.add(function(){d=h},c[3-a][2].disable,c[0][2].lock),g.add(b[3].fire),f[b[0]]=function(){return f[b[0]+"With"](this===f?void 0:this,arguments),this},f[b[0]+"With"]=g.fireWith}),e.promise(f),b&&b.call(f,f),f},when:function(a){var b=arguments.length,c=b,d=Array(c),e=f.call(arguments),g=r.Deferred(),h=function(a){return function(c){d[a]=this,e[a]=arguments.length>1?f.call(arguments):c,--b||g.resolveWith(d,e)}};if(b<=1&&(O(a,g.done(h(c)).resolve,g.reject),"pending"===g.state()||r.isFunction(e[c]&&e[c].then)))return g.then();while(c--)O(e[c],h(c),g.reject);return g.promise()}});var P=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;r.Deferred.exceptionHook=function(b,c){a.console&&a.console.warn&&b&&P.test(b.name)&&a.console.warn("jQuery.Deferred exception: "+b.message,b.stack,c)},r.readyException=function(b){a.setTimeout(function(){throw b})};var Q=r.Deferred();r.fn.ready=function(a){return Q.then(a)["catch"](function(a){r.readyException(a)}),this},r.extend({isReady:!1,readyWait:1,holdReady:function(a){a?r.readyWait++:r.ready(!0)},ready:function(a){(a===!0?--r.readyWait:r.isReady)||(r.isReady=!0,a!==!0&&--r.readyWait>0||Q.resolveWith(d,[r]))}}),r.ready.then=Q.then;function R(){d.removeEventListener("DOMContentLoaded",R),a.removeEventListener("load",R),r.ready()}"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(r.ready):(d.addEventListener("DOMContentLoaded",R),a.addEventListener("load",R));var S=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===r.type(c)){e=!0;for(h in c)S(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0, +r.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(r(a),c)})),b))for(;h1,null,!0)},removeData:function(a){return this.each(function(){W.remove(this,a)})}}),r.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=V.get(a,b),c&&(!d||r.isArray(c)?d=V.access(a,b,r.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=r.queue(a,b),d=c.length,e=c.shift(),f=r._queueHooks(a,b),g=function(){r.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return V.get(a,c)||V.access(a,c,{empty:r.Callbacks("once memory").add(function(){V.remove(a,[b+"queue",c])})})}}),r.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length\x20\t\r\n\f]+)/i,ja=/^$|\/(?:java|ecma)script/i,ka={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};ka.optgroup=ka.option,ka.tbody=ka.tfoot=ka.colgroup=ka.caption=ka.thead,ka.th=ka.td;function la(a,b){var c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&r.nodeName(a,b)?r.merge([a],c):c}function ma(a,b){for(var c=0,d=a.length;c-1)e&&e.push(f);else if(j=r.contains(f.ownerDocument,f),g=la(l.appendChild(f),"script"),j&&ma(g),c){k=0;while(f=g[k++])ja.test(f.type||"")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement("div")),c=d.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),o.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="",o.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var pa=d.documentElement,qa=/^key/,ra=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,sa=/^([^.]*)(?:\.(.+)|)/;function ta(){return!0}function ua(){return!1}function va(){try{return d.activeElement}catch(a){}}function wa(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)wa(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=ua;else if(!e)return a;return 1===f&&(g=e,e=function(a){return r().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=r.guid++)),a.each(function(){r.event.add(this,b,e,d,c)})}r.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=V.get(a);if(q){c.handler&&(f=c,c=f.handler,e=f.selector),e&&r.find.matchesSelector(pa,e),c.guid||(c.guid=r.guid++),(i=q.events)||(i=q.events={}),(g=q.handle)||(g=q.handle=function(b){return"undefined"!=typeof r&&r.event.triggered!==b.type?r.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(K)||[""],j=b.length;while(j--)h=sa.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n&&(l=r.event.special[n]||{},n=(e?l.delegateType:l.bindType)||n,l=r.event.special[n]||{},k=r.extend({type:n,origType:p,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&r.expr.match.needsContext.test(e),namespace:o.join(".")},f),(m=i[n])||(m=i[n]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,o,g)!==!1||a.addEventListener&&a.addEventListener(n,g)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),r.event.global[n]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=V.hasData(a)&&V.get(a);if(q&&(i=q.events)){b=(b||"").match(K)||[""],j=b.length;while(j--)if(h=sa.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n){l=r.event.special[n]||{},n=(d?l.delegateType:l.bindType)||n,m=i[n]||[],h=h[2]&&new RegExp("(^|\\.)"+o.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&p!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,o,q.handle)!==!1||r.removeEvent(a,n,q.handle),delete i[n])}else for(n in i)r.event.remove(a,n+b[j],c,d,!0);r.isEmptyObject(i)&&V.remove(a,"handle events")}},dispatch:function(a){var b=r.event.fix(a),c,d,e,f,g,h,i=new Array(arguments.length),j=(V.get(this,"events")||{})[b.type]||[],k=r.event.special[b.type]||{};for(i[0]=b,c=1;c-1:r.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h\x20\t\r\n\f]*)[^>]*)\/>/gi,ya=/\s*$/g;function Ca(a,b){return r.nodeName(a,"table")&&r.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a:a}function Da(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function Ea(a){var b=Aa.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Fa(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(V.hasData(a)&&(f=V.access(a),g=V.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;c1&&"string"==typeof q&&!o.checkClone&&za.test(q))return a.each(function(e){var f=a.eq(e);s&&(b[0]=q.call(this,e,f.html())),Ha(f,b,c,d)});if(m&&(e=oa(b,a[0].ownerDocument,!1,a,d),f=e.firstChild,1===e.childNodes.length&&(e=f),f||d)){for(h=r.map(la(e,"script"),Da),i=h.length;l")},clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=r.contains(a.ownerDocument,a);if(!(o.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||r.isXMLDoc(a)))for(g=la(h),f=la(a),d=0,e=f.length;d0&&ma(g,!i&&la(a,"script")),h},cleanData:function(a){for(var b,c,d,e=r.event.special,f=0;void 0!==(c=a[f]);f++)if(T(c)){if(b=c[V.expando]){if(b.events)for(d in b.events)e[d]?r.event.remove(c,d):r.removeEvent(c,d,b.handle);c[V.expando]=void 0}c[W.expando]&&(c[W.expando]=void 0)}}}),r.fn.extend({detach:function(a){return Ia(this,a,!0)},remove:function(a){return Ia(this,a)},text:function(a){return S(this,function(a){return void 0===a?r.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=a)})},null,a,arguments.length)},append:function(){return Ha(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ca(this,a);b.appendChild(a)}})},prepend:function(){return Ha(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ca(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return Ha(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return Ha(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(r.cleanData(la(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null!=a&&a,b=null==b?a:b,this.map(function(){return r.clone(this,a,b)})},html:function(a){return S(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!ya.test(a)&&!ka[(ia.exec(a)||["",""])[1].toLowerCase()]){a=r.htmlPrefilter(a);try{for(;c1)}});function Xa(a,b,c,d,e){return new Xa.prototype.init(a,b,c,d,e)}r.Tween=Xa,Xa.prototype={constructor:Xa,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||r.easing._default,this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(r.cssNumber[c]?"":"px")},cur:function(){var a=Xa.propHooks[this.prop];return a&&a.get?a.get(this):Xa.propHooks._default.get(this)},run:function(a){var b,c=Xa.propHooks[this.prop];return this.options.duration?this.pos=b=r.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Xa.propHooks._default.set(this),this}},Xa.prototype.init.prototype=Xa.prototype,Xa.propHooks={_default:{get:function(a){var b;return 1!==a.elem.nodeType||null!=a.elem[a.prop]&&null==a.elem.style[a.prop]?a.elem[a.prop]:(b=r.css(a.elem,a.prop,""),b&&"auto"!==b?b:0)},set:function(a){r.fx.step[a.prop]?r.fx.step[a.prop](a):1!==a.elem.nodeType||null==a.elem.style[r.cssProps[a.prop]]&&!r.cssHooks[a.prop]?a.elem[a.prop]=a.now:r.style(a.elem,a.prop,a.now+a.unit)}}},Xa.propHooks.scrollTop=Xa.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},r.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2},_default:"swing"},r.fx=Xa.prototype.init,r.fx.step={};var Ya,Za,$a=/^(?:toggle|show|hide)$/,_a=/queueHooks$/;function ab(){Za&&(a.requestAnimationFrame(ab),r.fx.tick())}function bb(){return a.setTimeout(function(){Ya=void 0}),Ya=r.now()}function cb(a,b){var c,d=0,e={height:a};for(b=b?1:0;d<4;d+=2-b)c=aa[d],e["margin"+c]=e["padding"+c]=a;return b&&(e.opacity=e.width=a),e}function db(a,b,c){for(var d,e=(gb.tweeners[b]||[]).concat(gb.tweeners["*"]),f=0,g=e.length;f1)},removeAttr:function(a){return this.each(function(){r.removeAttr(this,a)})}}),r.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return"undefined"==typeof a.getAttribute?r.prop(a,b,c):(1===f&&r.isXMLDoc(a)||(e=r.attrHooks[b.toLowerCase()]||(r.expr.match.bool.test(b)?hb:void 0)),void 0!==c?null===c?void r.removeAttr(a,b):e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:(a.setAttribute(b,c+""),c):e&&"get"in e&&null!==(d=e.get(a,b))?d:(d=r.find.attr(a,b),null==d?void 0:d))},attrHooks:{type:{set:function(a,b){if(!o.radioValue&&"radio"===b&&r.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}},removeAttr:function(a,b){var c,d=0,e=b&&b.match(K); +if(e&&1===a.nodeType)while(c=e[d++])a.removeAttribute(c)}}),hb={set:function(a,b,c){return b===!1?r.removeAttr(a,c):a.setAttribute(c,c),c}},r.each(r.expr.match.bool.source.match(/\w+/g),function(a,b){var c=ib[b]||r.find.attr;ib[b]=function(a,b,d){var e,f,g=b.toLowerCase();return d||(f=ib[g],ib[g]=e,e=null!=c(a,b,d)?g:null,ib[g]=f),e}});var jb=/^(?:input|select|textarea|button)$/i,kb=/^(?:a|area)$/i;r.fn.extend({prop:function(a,b){return S(this,r.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[r.propFix[a]||a]})}}),r.extend({prop:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return 1===f&&r.isXMLDoc(a)||(b=r.propFix[b]||b,e=r.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=r.find.attr(a,"tabindex");return b?parseInt(b,10):jb.test(a.nodeName)||kb.test(a.nodeName)&&a.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),o.optSelected||(r.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null},set:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}}),r.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){r.propFix[this.toLowerCase()]=this});var lb=/[\t\r\n\f]/g;function mb(a){return a.getAttribute&&a.getAttribute("class")||""}r.fn.extend({addClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).addClass(a.call(this,b,mb(this)))});if("string"==typeof a&&a){b=a.match(K)||[];while(c=this[i++])if(e=mb(c),d=1===c.nodeType&&(" "+e+" ").replace(lb," ")){g=0;while(f=b[g++])d.indexOf(" "+f+" ")<0&&(d+=f+" ");h=r.trim(d),e!==h&&c.setAttribute("class",h)}}return this},removeClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).removeClass(a.call(this,b,mb(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof a&&a){b=a.match(K)||[];while(c=this[i++])if(e=mb(c),d=1===c.nodeType&&(" "+e+" ").replace(lb," ")){g=0;while(f=b[g++])while(d.indexOf(" "+f+" ")>-1)d=d.replace(" "+f+" "," ");h=r.trim(d),e!==h&&c.setAttribute("class",h)}}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):r.isFunction(a)?this.each(function(c){r(this).toggleClass(a.call(this,c,mb(this),b),b)}):this.each(function(){var b,d,e,f;if("string"===c){d=0,e=r(this),f=a.match(K)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else void 0!==a&&"boolean"!==c||(b=mb(this),b&&V.set(this,"__className__",b),this.setAttribute&&this.setAttribute("class",b||a===!1?"":V.get(this,"__className__")||""))})},hasClass:function(a){var b,c,d=0;b=" "+a+" ";while(c=this[d++])if(1===c.nodeType&&(" "+mb(c)+" ").replace(lb," ").indexOf(b)>-1)return!0;return!1}});var nb=/\r/g,ob=/[\x20\t\r\n\f]+/g;r.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=r.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,r(this).val()):a,null==e?e="":"number"==typeof e?e+="":r.isArray(e)&&(e=r.map(e,function(a){return null==a?"":a+""})),b=r.valHooks[this.type]||r.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=r.valHooks[e.type]||r.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(nb,""):null==c?"":c)}}}),r.extend({valHooks:{option:{get:function(a){var b=r.find.attr(a,"value");return null!=b?b:r.trim(r.text(a)).replace(ob," ")}},select:{get:function(a){for(var b,c,d=a.options,e=a.selectedIndex,f="select-one"===a.type,g=f?null:[],h=f?e+1:d.length,i=e<0?h:f?e:0;i-1)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),r.each(["radio","checkbox"],function(){r.valHooks[this]={set:function(a,b){if(r.isArray(b))return a.checked=r.inArray(r(a).val(),b)>-1}},o.checkOn||(r.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var pb=/^(?:focusinfocus|focusoutblur)$/;r.extend(r.event,{trigger:function(b,c,e,f){var g,h,i,j,k,m,n,o=[e||d],p=l.call(b,"type")?b.type:b,q=l.call(b,"namespace")?b.namespace.split("."):[];if(h=i=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!pb.test(p+r.event.triggered)&&(p.indexOf(".")>-1&&(q=p.split("."),p=q.shift(),q.sort()),k=p.indexOf(":")<0&&"on"+p,b=b[r.expando]?b:new r.Event(p,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=q.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:r.makeArray(c,[b]),n=r.event.special[p]||{},f||!n.trigger||n.trigger.apply(e,c)!==!1)){if(!f&&!n.noBubble&&!r.isWindow(e)){for(j=n.delegateType||p,pb.test(j+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),i=h;i===(e.ownerDocument||d)&&o.push(i.defaultView||i.parentWindow||a)}g=0;while((h=o[g++])&&!b.isPropagationStopped())b.type=g>1?j:n.bindType||p,m=(V.get(h,"events")||{})[b.type]&&V.get(h,"handle"),m&&m.apply(h,c),m=k&&h[k],m&&m.apply&&T(h)&&(b.result=m.apply(h,c),b.result===!1&&b.preventDefault());return b.type=p,f||b.isDefaultPrevented()||n._default&&n._default.apply(o.pop(),c)!==!1||!T(e)||k&&r.isFunction(e[p])&&!r.isWindow(e)&&(i=e[k],i&&(e[k]=null),r.event.triggered=p,e[p](),r.event.triggered=void 0,i&&(e[k]=i)),b.result}},simulate:function(a,b,c){var d=r.extend(new r.Event,c,{type:a,isSimulated:!0});r.event.trigger(d,null,b)}}),r.fn.extend({trigger:function(a,b){return this.each(function(){r.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];if(c)return r.event.trigger(a,b,c,!0)}}),r.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(a,b){r.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),r.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),o.focusin="onfocusin"in a,o.focusin||r.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){r.event.simulate(b,a.target,r.event.fix(a))};r.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=V.access(d,b);e||d.addEventListener(a,c,!0),V.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=V.access(d,b)-1;e?V.access(d,b,e):(d.removeEventListener(a,c,!0),V.remove(d,b))}}});var qb=a.location,rb=r.now(),sb=/\?/;r.parseXML=function(b){var c;if(!b||"string"!=typeof b)return null;try{c=(new a.DOMParser).parseFromString(b,"text/xml")}catch(d){c=void 0}return c&&!c.getElementsByTagName("parsererror").length||r.error("Invalid XML: "+b),c};var tb=/\[\]$/,ub=/\r?\n/g,vb=/^(?:submit|button|image|reset|file)$/i,wb=/^(?:input|select|textarea|keygen)/i;function xb(a,b,c,d){var e;if(r.isArray(b))r.each(b,function(b,e){c||tb.test(a)?d(a,e):xb(a+"["+("object"==typeof e&&null!=e?b:"")+"]",e,c,d)});else if(c||"object"!==r.type(b))d(a,b);else for(e in b)xb(a+"["+e+"]",b[e],c,d)}r.param=function(a,b){var c,d=[],e=function(a,b){var c=r.isFunction(b)?b():b;d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(null==c?"":c)};if(r.isArray(a)||a.jquery&&!r.isPlainObject(a))r.each(a,function(){e(this.name,this.value)});else for(c in a)xb(c,a[c],b,e);return d.join("&")},r.fn.extend({serialize:function(){return r.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=r.prop(this,"elements");return a?r.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!r(this).is(":disabled")&&wb.test(this.nodeName)&&!vb.test(a)&&(this.checked||!ha.test(a))}).map(function(a,b){var c=r(this).val();return null==c?null:r.isArray(c)?r.map(c,function(a){return{name:b.name,value:a.replace(ub,"\r\n")}}):{name:b.name,value:c.replace(ub,"\r\n")}}).get()}});var yb=/%20/g,zb=/#.*$/,Ab=/([?&])_=[^&]*/,Bb=/^(.*?):[ \t]*([^\r\n]*)$/gm,Cb=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Db=/^(?:GET|HEAD)$/,Eb=/^\/\//,Fb={},Gb={},Hb="*/".concat("*"),Ib=d.createElement("a");Ib.href=qb.href;function Jb(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(K)||[];if(r.isFunction(c))while(d=f[e++])"+"===d[0]?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function Kb(a,b,c,d){var e={},f=a===Gb;function g(h){var i;return e[h]=!0,r.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function Lb(a,b){var c,d,e=r.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&r.extend(!0,a,d),a}function Mb(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader("Content-Type"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+" "+i[0]]){f=e;break}g||(g=e)}f=f||g}if(f)return f!==i[0]&&i.unshift(f),c[f]}function Nb(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}r.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:qb.href,type:"GET",isLocal:Cb.test(qb.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Hb,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":r.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?Lb(Lb(a,r.ajaxSettings),b):Lb(r.ajaxSettings,a)},ajaxPrefilter:Jb(Fb),ajaxTransport:Jb(Gb),ajax:function(b,c){"object"==typeof b&&(c=b,b=void 0),c=c||{};var e,f,g,h,i,j,k,l,m,n,o=r.ajaxSetup({},c),p=o.context||o,q=o.context&&(p.nodeType||p.jquery)?r(p):r.event,s=r.Deferred(),t=r.Callbacks("once memory"),u=o.statusCode||{},v={},w={},x="canceled",y={readyState:0,getResponseHeader:function(a){var b;if(k){if(!h){h={};while(b=Bb.exec(g))h[b[1].toLowerCase()]=b[2]}b=h[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return k?g:null},setRequestHeader:function(a,b){return null==k&&(a=w[a.toLowerCase()]=w[a.toLowerCase()]||a,v[a]=b),this},overrideMimeType:function(a){return null==k&&(o.mimeType=a),this},statusCode:function(a){var b;if(a)if(k)y.always(a[y.status]);else for(b in a)u[b]=[u[b],a[b]];return this},abort:function(a){var b=a||x;return e&&e.abort(b),A(0,b),this}};if(s.promise(y),o.url=((b||o.url||qb.href)+"").replace(Eb,qb.protocol+"//"),o.type=c.method||c.type||o.method||o.type,o.dataTypes=(o.dataType||"*").toLowerCase().match(K)||[""],null==o.crossDomain){j=d.createElement("a");try{j.href=o.url,j.href=j.href,o.crossDomain=Ib.protocol+"//"+Ib.host!=j.protocol+"//"+j.host}catch(z){o.crossDomain=!0}}if(o.data&&o.processData&&"string"!=typeof o.data&&(o.data=r.param(o.data,o.traditional)),Kb(Fb,o,c,y),k)return y;l=r.event&&o.global,l&&0===r.active++&&r.event.trigger("ajaxStart"),o.type=o.type.toUpperCase(),o.hasContent=!Db.test(o.type),f=o.url.replace(zb,""),o.hasContent?o.data&&o.processData&&0===(o.contentType||"").indexOf("application/x-www-form-urlencoded")&&(o.data=o.data.replace(yb,"+")):(n=o.url.slice(f.length),o.data&&(f+=(sb.test(f)?"&":"?")+o.data,delete o.data),o.cache===!1&&(f=f.replace(Ab,""),n=(sb.test(f)?"&":"?")+"_="+rb++ +n),o.url=f+n),o.ifModified&&(r.lastModified[f]&&y.setRequestHeader("If-Modified-Since",r.lastModified[f]),r.etag[f]&&y.setRequestHeader("If-None-Match",r.etag[f])),(o.data&&o.hasContent&&o.contentType!==!1||c.contentType)&&y.setRequestHeader("Content-Type",o.contentType),y.setRequestHeader("Accept",o.dataTypes[0]&&o.accepts[o.dataTypes[0]]?o.accepts[o.dataTypes[0]]+("*"!==o.dataTypes[0]?", "+Hb+"; q=0.01":""):o.accepts["*"]);for(m in o.headers)y.setRequestHeader(m,o.headers[m]);if(o.beforeSend&&(o.beforeSend.call(p,y,o)===!1||k))return y.abort();if(x="abort",t.add(o.complete),y.done(o.success),y.fail(o.error),e=Kb(Gb,o,c,y)){if(y.readyState=1,l&&q.trigger("ajaxSend",[y,o]),k)return y;o.async&&o.timeout>0&&(i=a.setTimeout(function(){y.abort("timeout")},o.timeout));try{k=!1,e.send(v,A)}catch(z){if(k)throw z;A(-1,z)}}else A(-1,"No Transport");function A(b,c,d,h){var j,m,n,v,w,x=c;k||(k=!0,i&&a.clearTimeout(i),e=void 0,g=h||"",y.readyState=b>0?4:0,j=b>=200&&b<300||304===b,d&&(v=Mb(o,y,d)),v=Nb(o,v,y,j),j?(o.ifModified&&(w=y.getResponseHeader("Last-Modified"),w&&(r.lastModified[f]=w),w=y.getResponseHeader("etag"),w&&(r.etag[f]=w)),204===b||"HEAD"===o.type?x="nocontent":304===b?x="notmodified":(x=v.state,m=v.data,n=v.error,j=!n)):(n=x,!b&&x||(x="error",b<0&&(b=0))),y.status=b,y.statusText=(c||x)+"",j?s.resolveWith(p,[m,x,y]):s.rejectWith(p,[y,x,n]),y.statusCode(u),u=void 0,l&&q.trigger(j?"ajaxSuccess":"ajaxError",[y,o,j?m:n]),t.fireWith(p,[y,x]),l&&(q.trigger("ajaxComplete",[y,o]),--r.active||r.event.trigger("ajaxStop")))}return y},getJSON:function(a,b,c){return r.get(a,b,c,"json")},getScript:function(a,b){return r.get(a,void 0,b,"script")}}),r.each(["get","post"],function(a,b){r[b]=function(a,c,d,e){return r.isFunction(c)&&(e=e||d,d=c,c=void 0),r.ajax(r.extend({url:a,type:b,dataType:e,data:c,success:d},r.isPlainObject(a)&&a))}}),r._evalUrl=function(a){return r.ajax({url:a,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},r.fn.extend({wrapAll:function(a){var b;return this[0]&&(r.isFunction(a)&&(a=a.call(this[0])),b=r(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this},wrapInner:function(a){return r.isFunction(a)?this.each(function(b){r(this).wrapInner(a.call(this,b))}):this.each(function(){var b=r(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=r.isFunction(a);return this.each(function(c){r(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(a){return this.parent(a).not("body").each(function(){r(this).replaceWith(this.childNodes)}),this}}),r.expr.pseudos.hidden=function(a){return!r.expr.pseudos.visible(a)},r.expr.pseudos.visible=function(a){return!!(a.offsetWidth||a.offsetHeight||a.getClientRects().length)},r.ajaxSettings.xhr=function(){try{return new a.XMLHttpRequest}catch(b){}};var Ob={0:200,1223:204},Pb=r.ajaxSettings.xhr();o.cors=!!Pb&&"withCredentials"in Pb,o.ajax=Pb=!!Pb,r.ajaxTransport(function(b){var c,d;if(o.cors||Pb&&!b.crossDomain)return{send:function(e,f){var g,h=b.xhr();if(h.open(b.type,b.url,b.async,b.username,b.password),b.xhrFields)for(g in b.xhrFields)h[g]=b.xhrFields[g];b.mimeType&&h.overrideMimeType&&h.overrideMimeType(b.mimeType),b.crossDomain||e["X-Requested-With"]||(e["X-Requested-With"]="XMLHttpRequest");for(g in e)h.setRequestHeader(g,e[g]);c=function(a){return function(){c&&(c=d=h.onload=h.onerror=h.onabort=h.onreadystatechange=null,"abort"===a?h.abort():"error"===a?"number"!=typeof h.status?f(0,"error"):f(h.status,h.statusText):f(Ob[h.status]||h.status,h.statusText,"text"!==(h.responseType||"text")||"string"!=typeof h.responseText?{binary:h.response}:{text:h.responseText},h.getAllResponseHeaders()))}},h.onload=c(),d=h.onerror=c("error"),void 0!==h.onabort?h.onabort=d:h.onreadystatechange=function(){4===h.readyState&&a.setTimeout(function(){c&&d()})},c=c("abort");try{h.send(b.hasContent&&b.data||null)}catch(i){if(c)throw i}},abort:function(){c&&c()}}}),r.ajaxPrefilter(function(a){a.crossDomain&&(a.contents.script=!1)}),r.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(a){return r.globalEval(a),a}}}),r.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET")}),r.ajaxTransport("script",function(a){if(a.crossDomain){var b,c;return{send:function(e,f){b=r(" +{% endblock %} +{% block navigation-bar %} + > users + > add user +{% endblock %} +{% block content %} +
Add User
+
+
{% csrf_token %} +
+
Name
+ +
+
+
Surname
+ +
+
+
Username
+ + + +
+
+
Password
+ + +
+
+
Repeat
+ + +
+
+ +
+
+
+{% endblock %} \ No newline at end of file diff --git a/frontend/iva/templates/iva/alert_log.html b/frontend/iva/templates/iva/alert_log.html new file mode 100644 index 0000000..b049db5 --- /dev/null +++ b/frontend/iva/templates/iva/alert_log.html @@ -0,0 +1,10 @@ +{% load staticfiles %} + + + +
Alert Log
+
+{% for entry in log %} +
{{ entry }}
+{% endfor %} +
\ No newline at end of file diff --git a/frontend/iva/templates/iva/alert_notes.html b/frontend/iva/templates/iva/alert_notes.html new file mode 100644 index 0000000..2d16561 --- /dev/null +++ b/frontend/iva/templates/iva/alert_notes.html @@ -0,0 +1,14 @@ +{% load staticfiles %} + + + +
Alert Notes
+
+
{% csrf_token %} + +
+ +
+ +
+
diff --git a/frontend/iva/templates/iva/alert_status.html b/frontend/iva/templates/iva/alert_status.html new file mode 100644 index 0000000..1e78eb4 --- /dev/null +++ b/frontend/iva/templates/iva/alert_status.html @@ -0,0 +1,29 @@ +{% load staticfiles %} + + + +
Change Alert Status
+ +
+
{% csrf_token %} +
+
Current Status
+
{{ current_status }}
+
+
+
New Status
+ +
+
+ +
+ +
+
+ diff --git a/frontend/iva/templates/iva/alerts.html b/frontend/iva/templates/iva/alerts.html new file mode 100644 index 0000000..a6021a7 --- /dev/null +++ b/frontend/iva/templates/iva/alerts.html @@ -0,0 +1,116 @@ +{% extends "iva/main_template.html" %} +{% load staticfiles %} +{% load custom_filters %} +{% block title %}Vulnerable Software{% endblock %} +{% block css %} + +{% endblock %} +{% block js %} + {% include "iva/jplist_includes.html" %} + +{% endblock %} +{% block navigation-bar %} + > Alerts +{% endblock %} +{% block content %} +
Alerts
+
+
+
+
+
+ {% include 'iva/jplist_head.html' %} +


+ {% csrf_token %} + + + + + {% for alert in alerts %} + + + + + + + + + + + {% endfor %} +
Generated onSoftwarseCPECVEsOptionsStatus
{{alert.generated_on}} + Vendor {{alert.vendor}}
+ Product {{alert.product}}
+ Version {{alert.version}} +
+ + + + + +
{{ alert.uri | decode_uri_binding }} + + + +
+
+
+ + {% for cve_id in alert.cves %} + + + + + {% endfor %} +
+ {{ cve_id }} + + +
+
+
+ {% if alert.status != 'removed' and alert.status != 'closed' %} + + {% endif %} + + + + + + + + + + {% if alert.status == 'new' %} + + {% elif alert.status == 'removed' %} + + {% elif alert.status == 'closed' %} + + {% elif alert.status == 'sent' %} + + {% endif %} +
+ {% include 'iva/jplist_foot.html' %} +
+
+{% endblock %} \ No newline at end of file diff --git a/frontend/iva/templates/iva/assign_cpe.html b/frontend/iva/templates/iva/assign_cpe.html new file mode 100644 index 0000000..4d3fcb2 --- /dev/null +++ b/frontend/iva/templates/iva/assign_cpe.html @@ -0,0 +1,17 @@ +{% extends "iva/main_template.html" %} +{% load staticfiles %} +{% block navigation-bar %} + > new software + > assign CPE +{% endblock %} +{% block title %}Assign CPE{% endblock %} +{% block js %} + {% include "iva/jplist_includes.html" %} + + +{% endblock %} +{% block content %} + {% include "iva/sw_product.html" %} + {% include "iva/assign_cpe_form.html" %} + {% include "iva/cpe_matches.html" %} +{% endblock %} diff --git a/frontend/iva/templates/iva/assign_cpe_form.html b/frontend/iva/templates/iva/assign_cpe_form.html new file mode 100644 index 0000000..aa381c4 --- /dev/null +++ b/frontend/iva/templates/iva/assign_cpe_form.html @@ -0,0 +1,21 @@ +{% load static %} +
+ CPE +
+ + + Common Platform Enumeration (CPE)
+ CPE is a method to identify classes of installed IT products (applications, operating systems, and hardware devices) + inside an organization. CPE is compatible with the CVE standard, and therefore, it allows to automatically + search for vulnerabilities on IT assets.

+ Well-Formed CPE Name Model
+ The logical structure for a CPE identifier, which is assigned to a software product, is defined by a set + of attributes (e.g. vendor, product, version, edition, etc.) and their values. The form below must be + filled with values that better match the information provided by the inventory database for that software product. +
+
+
+
+ {% include 'iva/cpe_form.html' with form_action='assign_cpe.html' form_name='assign_cpe' button=''%} +
+
\ No newline at end of file diff --git a/frontend/iva/templates/iva/change_daily_db_update_time.html b/frontend/iva/templates/iva/change_daily_db_update_time.html new file mode 100644 index 0000000..54a0152 --- /dev/null +++ b/frontend/iva/templates/iva/change_daily_db_update_time.html @@ -0,0 +1,39 @@ +{% load staticfiles %} +{% load custom_filters %} + + + + Change Daily DB Update Time + + + + +
Change Daily DB Update Time
+
+
{% csrf_token %} +
+
Hour
+ +
+
+
Minute
+ +
+
+ +
+ +
+
+ + \ No newline at end of file diff --git a/frontend/iva/templates/iva/change_user_pwd.html b/frontend/iva/templates/iva/change_user_pwd.html new file mode 100644 index 0000000..d9a13cc --- /dev/null +++ b/frontend/iva/templates/iva/change_user_pwd.html @@ -0,0 +1,40 @@ +{% load staticfiles %} + + + + Change User Password + + + + + + +
Change User Password
+
+
{% csrf_token %} +
+
Old Password
+ + +
+
+
New Password
+ + +
+
+
Repeat
+ + +
+
+ +
+ +
+ {% if error %} +
An error occurred when updating the password of user {{ username }}
+ {% endif %} +
+ + \ No newline at end of file diff --git a/frontend/iva/templates/iva/compare_cpe.html b/frontend/iva/templates/iva/compare_cpe.html new file mode 100644 index 0000000..91e3c90 --- /dev/null +++ b/frontend/iva/templates/iva/compare_cpe.html @@ -0,0 +1,103 @@ +{% load staticfiles %} + + + + + +
{{coincidence}} % of coincidence
+ +
+ + + + + + {% if 'vendor' in not_matches %} + + {% else %} + + {% endif %} + + + + + {% if 'product' in not_matches %} + + {% else %} + + {% endif %} + + + + + {% if 'version' in not_matches %} + + {% else %} + + {% endif %} + + + + + {% if 'update' in not_matches %} + + {% else %} + + {% endif %} + + + + + {% if 'edition' in not_matches %} + + {% else %} + + {% endif %} + + + + + {% if 'language' in not_matches %} + + {% else %} + + {% endif %} + + + + + {% if 'sw_edition' in not_matches %} + + {% else %} + + {% endif %} + + + + + {% if 'target_sw' in not_matches %} + + {% else %} + + {% endif %} + + + + + {% if 'target_hw' in not_matches %} + + {% else %} + + {% endif %} + + + + + {% if 'other' in not_matches %} + + {% else %} + + {% endif %} + +
AttributeSoftware CPECVE match CPE
Vendor{{wfn_a.vendor}}{{wfn_b.vendor}}{{wfn_b.vendor}}
Product{{wfn_a.product}}{{wfn_b.product}}{{wfn_b.product}}
Version{{wfn_a.version}}{{wfn_b.version}}{{wfn_b.version}}
Update{{wfn_a.update}}{{wfn_b.update}}{{wfn_b.update}}
Edition{{wfn_a.edition}}{{wfn_b.edition}}{{wfn_b.edition}}
Language{{wfn_a.language}}{{wfn_b.language}}{{wfn_b.language}}
Software Edition{{wfn_a.sw_edition}}{{wfn_b.sw_edition}}{{wfn_b.sw_edition}}
Target Software{{wfn_a.target_sw}}{{wfn_b.target_sw}}{{wfn_b.target_sw}}
Target Hardware{{wfn_a.target_hw}}{{wfn_b.target_hw}}{{wfn_b.target_hw}}
Other{{wfn_a.other}}{{wfn_b.other}}{{wfn_b.other}}
+
\ No newline at end of file diff --git a/frontend/iva/templates/iva/cpe_form.html b/frontend/iva/templates/iva/cpe_form.html new file mode 100644 index 0000000..8c0ffff --- /dev/null +++ b/frontend/iva/templates/iva/cpe_form.html @@ -0,0 +1,205 @@ +{% load staticfiles %} +{% load custom_tags %} +
{% csrf_token %} +
+
+ Logical Values ? + + Logical Values
+ 'ANY', '*', or 'empty'
+ These values are assigned when there are NO restrictions on acceptable values for an attribute (e.g. Update). +
The logical value '*' can be used in the version attribute to indicate that the + version is equal to its subsets. For example, the version 1.2.* is equal to 1.2.67 or 1.2.3.89. +

+ 'NA' or '-'
+ These logical values are assigned when there is NO meaningful value for an attribute, or when + this attribute is not part of the products's description (Not Applicable). +
+
+
+ +
+
+
+
Type
+ +
+ + + The attribute "Type" specifies the type of IT asset: software (a), + operating system (o), or hardware device (h). + +
+
+ +
+
Vendor
+ +
+ + + Values for this attribute describe or identify the person or organization that manufactured or created the product. + +
+
+ +
+
Product
+ +
+ + + Values for this attribute describe or identify the most common and recognizable title or name of the product. + +
+
+ +
+
Version
+ +
+ + + Values for this attribute are vendor-specific alphanumeric strings characterizing the particular release version of the product. +
The logical value '*' can be used for this attribute to indicate that the + version is equal to its subsets. For example, the version 1.2.* is equal to 1.2.67 or 1.2.3.89. +
+
+
+ +
+
Update
+ +
+ + + Values for this attribute are vendor-specific alphanumeric strings characterizing the + particular update, service pack, or point release of the product. + +
+
+ +
+
Edition
+ +
+ + + Values for this attribute should capture edition-related terms applied by the vendor to the product. + +
+
+ +
+ {{ button }} +
+ +
+ +
+
+
Language
+ +
+ + + Values for this attribute are valid language tags as defined by [RFC5646], and are used + to define the language supported in the user interface of the product being described. + +
+
+ +
+
Software Edition
+ +
+ + + Values for this attribute characterize how the product is tailored to a particular market or + class of end users (e.g. student or professional edition). + +
+
+ +
+
Target Software
+ +
+ + + Values for this attribute characterize the software computing environment within + which the product operates (e.g. Windows or Linux). + +
+
+ +
+
Target Hardware
+ +
+ + + Values for this attribute characterize the instruction set architecture (e.g., x86) + on which the product operates. Bytecode-intermediate languages, such as Java bytecode for + the Java Virtual Machine or Microsoft Common Intermediate Language for the Common Language + Runtime virtual machine, shall be considered instruction set architectures. + +
+
+ +
+
Other
+ +
+ + + Values for this attribute capture any other general descriptive or identifying information + which is vendor- or product-specific and which does not logically fit in any other attribute value. + +
+
+ +
+
URI
+
{{ uri_binging }}
+
+ + + The URI is formed using the values of the attributes defined is this form following the + specifications described in the Common Platform Enumeration Naming Specification (Version 2.3). + The URI represents an identifier for an IT asset(s). + +
+
+
+
+ + +
\ No newline at end of file diff --git a/frontend/iva/templates/iva/cpe_matches.html b/frontend/iva/templates/iva/cpe_matches.html new file mode 100644 index 0000000..baf8828 --- /dev/null +++ b/frontend/iva/templates/iva/cpe_matches.html @@ -0,0 +1,36 @@ +{% load staticfiles %} +
+
+ CPE Candidates +
+ + + This box shows a list of CPE candidates for that software product. To see + all the values (e.g. target hardware) of a CPE, click on the eye icon. + To select a CPE, click on the URI string. + +
+
+
+
+ {% include 'iva/jplist_head.html' %} +


+ + + + + {% load custom_filters %} + {% for cpe in cpe_matches %} + + + + + {% endfor %} +
CPE URIOptions
{{cpe.uri_binding | decode_uri_binding}} + + + +
+ {% include 'iva/jplist_foot.html' %} +
+
\ No newline at end of file diff --git a/frontend/iva/templates/iva/cpe_wfn.html b/frontend/iva/templates/iva/cpe_wfn.html new file mode 100644 index 0000000..ad00438 --- /dev/null +++ b/frontend/iva/templates/iva/cpe_wfn.html @@ -0,0 +1,63 @@ +{% load staticfiles %} + + + +
CPE Well-Formed Name
+ +
+
+
URI binding
+
{{uri_binging}}
+
+
+
Type
+ {% if wfn.part == 'a' %} +
Software
+ {% elif wfn.part == 'o' %} +
Operating System
+ {% elif wfn.part == 'h' %} +
Hardware Device
+ {% endif %} +
+
+
Vendor
+
{{wfn.vendor}}
+
+ {% load custom_filters %} +
+
Product
+
{{wfn.product}}
+
+
+
Version
+
{{wfn.version}}
+
+
+
Update
+
{{wfn.update}}
+
+
+
Edition
+
{{wfn.edition}}
+
+
+
Software Edition
+
{{wfn.sw_edition}}
+
+
+
Target Software
+
{{wfn.target_sw}}
+
+
+
Target Hardware
+
{{wfn.target_hw}}
+
+
+
Language
+
{{wfn.language}}
+
+
+
Other
+
{{wfn.other}}
+
+
\ No newline at end of file diff --git a/frontend/iva/templates/iva/cve_match_cpe_entries.html b/frontend/iva/templates/iva/cve_match_cpe_entries.html new file mode 100644 index 0000000..9554236 --- /dev/null +++ b/frontend/iva/templates/iva/cve_match_cpe_entries.html @@ -0,0 +1,21 @@ +{% load custom_filters %} +{% load staticfiles %} + \ No newline at end of file diff --git a/frontend/iva/templates/iva/cve_matches.html b/frontend/iva/templates/iva/cve_matches.html new file mode 100644 index 0000000..112c038 --- /dev/null +++ b/frontend/iva/templates/iva/cve_matches.html @@ -0,0 +1,140 @@ +{% extends "iva/main_template.html" %} +{% load staticfiles %} +{% load custom_filters %} +{% block js %} + {% include "iva/jplist_includes.html" %} + +{% endblock %} +{% block title %}CVE Matches{% endblock %} +{% block navigation-bar %}> cve matches{% endblock %} +{% block content %} +
CVE Matches
+
+
+ Filters +
+
Vendor
+ +
+
+
Product
+ +
+
+
Show removed
+ +
+
+
+ {% include 'iva/jplist_head.html' %} +


+ {% csrf_token %} + + + + + + + {% for match in cve_matches %} + + + + + + + + + + {% endfor %} +
+ CVE + + + + + + + ConfirmedRemovedVendorProductVersionCPE
+ + + + + {% if match.positive == 0 and match.removed == 0 %} + + {% endif %} + {% if match.positive == 1 and match.removed == 0 %} + + {% endif %} + {% if match.removed == 0 and match.positive == 0 %} + + {% endif %} + {% if match.removed == 1 and match.positive == 0 %} + + {% endif %} + + +
{{ match.cve_id }} +
+ +
+ +
+ + + + + + + + + + + +
+ +
{% if match.positive == 0 %}No{% else %}Yes{% endif %}{% if match.removed == 0 %}No{% else %}Yes{% endif %}{{ match.software.vendor }}{{ match.software.product }}{{ match.software.version }} + {{ match.software.cpe.uri_binding | decode_uri_binding }} + + + +
+ {% include 'iva/jplist_foot.html' %} +
+
+{% endblock %} \ No newline at end of file diff --git a/frontend/iva/templates/iva/grouped_cve_matches.html b/frontend/iva/templates/iva/grouped_cve_matches.html new file mode 100644 index 0000000..15b25ff --- /dev/null +++ b/frontend/iva/templates/iva/grouped_cve_matches.html @@ -0,0 +1,94 @@ +{% extends "iva/main_template.html" %} +{% load staticfiles %} +{% load custom_filters %} +{% block js %} + {% include "iva/jplist_includes.html" %} + +{% endblock %} +{% block navigation-bar %} + > cve matches + > grouped cve matches +{% endblock %} +{% block title %}Grouped CVE Matches{% endblock %} +{% block content %} + {% csrf_token %} + {% include "iva/sw_product.html" %} +
Grouped CVE Matches
+
+ + + + + + + + + + + + + + +
CVE Matches ({{ cve_matches|length }}) +
+ + + CVEs that have exactly the same CPE entries related to the software product. + +
+
CPE Entries +
+ + + Common CPEs in the CVEs. Note that a CVE can have other CPEs. However, only those CPEs, + which are relevant for the software product are, considered. + +
+
Options +
+ + + You can either discard or confirm all the CVEs for the software product. + +
+
+ {% for cve_match in cve_matches %} + + {{ cve_match.cve_id }} + || + {% endfor %} + + + {% for cpe_entry in cve_matches.0.cpe_entries %} + + + + + + + {% endfor %} +
{{ cpe_entry }} + + + + + + + +
+
+ {% if cve_matches.0.positive == 0 and cve_matches.0.removed == 0 %} + + {% elif cve_matches.0.positive == 1 and cve_matches.0.removed == 0 %} + + {% endif %} + + {% if cve_matches.0.removed == 0 and cve_matches.0.positive == 0 %} + + {% elif cve_matches.0.removed == 1 and cve_matches.0.positive == 0 %} + + {% endif %} +
+
+
+{% endblock %} \ No newline at end of file diff --git a/frontend/iva/templates/iva/index.html b/frontend/iva/templates/iva/index.html new file mode 100644 index 0000000..6176b3e --- /dev/null +++ b/frontend/iva/templates/iva/index.html @@ -0,0 +1,2 @@ +{% extends "iva/main_template.html" %} +{% block title %}Main Menu{% endblock %} diff --git a/frontend/iva/templates/iva/jplist_foot.html b/frontend/iva/templates/iva/jplist_foot.html new file mode 100644 index 0000000..e2aa5bf --- /dev/null +++ b/frontend/iva/templates/iva/jplist_foot.html @@ -0,0 +1,23 @@ + +
+

No results found

+
+ + +
+ + jPList Actions +
+ + +
+ +
+
+ +
+
+ +
\ No newline at end of file diff --git a/frontend/iva/templates/iva/jplist_head.html b/frontend/iva/templates/iva/jplist_head.html new file mode 100644 index 0000000..daea602 --- /dev/null +++ b/frontend/iva/templates/iva/jplist_head.html @@ -0,0 +1,16 @@ + +
+ + jPList Actions +
+ +
+
+
+ +
+
+
\ No newline at end of file diff --git a/frontend/iva/templates/iva/jplist_includes.html b/frontend/iva/templates/iva/jplist_includes.html new file mode 100644 index 0000000..e9eac96 --- /dev/null +++ b/frontend/iva/templates/iva/jplist_includes.html @@ -0,0 +1,15 @@ +{% load staticfiles %} + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/iva/templates/iva/local_repositories.html b/frontend/iva/templates/iva/local_repositories.html new file mode 100644 index 0000000..f7fb3f7 --- /dev/null +++ b/frontend/iva/templates/iva/local_repositories.html @@ -0,0 +1,102 @@ +{% extends "iva/main_template.html" %} +{% load staticfiles %} +{% block title %} Local Repositories {% endblock %} +{% block js %} {% endblock %} +{% block navigation-bar %} > update database {% endblock %} +{% block content %} + {% csrf_token %} +
+ DB Status +
+ + + + +
+
+
+
+ cve-search DB +
+
CPE Entries
+
{{ cve_search_cpe_entries }}
+
+
+
CVE Entries
+
{{ cve_search_cve_entries }}
+
+
+
+ IVA DB +
+
CPE Entries
+
{{ iva_cpe_entries }}
+
+
+
CVE Entries
+
{{ iva_cve_entries }}
+
+
+
+
+ {% if not db_populated and not is_repopulation_running %} + {% if is_population_running %} +
+ Database is being populated. +
+ {% else %} + + {% endif %} + {% else %} + {% if is_repopulation_running %} +
+ Database is being repopulated. +
+ {% else %} + + {% endif %} + {% endif %} +
+
+
+ Daily DB Update +
+ + + Here you can schedule the time (hour and minute) when the CPE and CVE entries will be updated in the database. + Moreover, after the update, CVEs matches for each software product (with assigned CPE) are searched automatically. + This ensures that the user always sees the most current CVE matches for all products, without manually searching + for new CVEs. The daily update can be activated/deactivated. + +
+
+
+
+
Last Update
+
{{ task.last_execution }}
+
+
+
Next Update
+
{{ task.next_execution }} + + + +
+
+
+
Scheduled
+
{{ task.is_scheduled }}
+
+
+
Running
+
{{ task.is_running }}
+
+
+ {% if task.status == 'active' %} + + {% else %} + + {% endif %} +
+
+{% endblock %} \ No newline at end of file diff --git a/frontend/iva/templates/iva/main_template.html b/frontend/iva/templates/iva/main_template.html new file mode 100644 index 0000000..36933a7 --- /dev/null +++ b/frontend/iva/templates/iva/main_template.html @@ -0,0 +1,25 @@ +{% load staticfiles %} + + + + {% block title %}Template{% endblock %} + + + {% block js %}{% endblock %} + {% block css %}{% endblock %} + + + + + + {% include "iva/menu.html" %} +
+ + {% block content %}{% endblock %} +
+ + + \ No newline at end of file diff --git a/frontend/iva/templates/iva/menu.html b/frontend/iva/templates/iva/menu.html new file mode 100644 index 0000000..9a050ca --- /dev/null +++ b/frontend/iva/templates/iva/menu.html @@ -0,0 +1,15 @@ +{% load custom_tags %} +{% load static %} + \ No newline at end of file diff --git a/frontend/iva/templates/iva/modify_cpe.html b/frontend/iva/templates/iva/modify_cpe.html new file mode 100644 index 0000000..9e19d30 --- /dev/null +++ b/frontend/iva/templates/iva/modify_cpe.html @@ -0,0 +1,19 @@ +{% extends "iva/main_template.html" %} +{% load staticfiles %} +{% load custom_filters %} +{% load custom_tags %} +{% block js %} {% endblock %} +{% block css %} {% endblock %} +{% block title %}Modify CPE{% endblock %} +{% block navigation-bar %} + > software with CPE + > modify CPE +{% endblock %} +{% block content %} + {% include "iva/sw_product.html" %} +
CPE
+
+ {% include 'iva/cpe_form.html' with form_action='modify_cpe.html' form_name='modify_cpe' button='' %} + {% if updated %}
successfully updated
{% endif %} +
+{% endblock %} \ No newline at end of file diff --git a/frontend/iva/templates/iva/modify_user.html b/frontend/iva/templates/iva/modify_user.html new file mode 100644 index 0000000..e85c9e3 --- /dev/null +++ b/frontend/iva/templates/iva/modify_user.html @@ -0,0 +1,36 @@ +{% load staticfiles %} + + + + Modify User + + + + + + +
Modify User
+
+
{% csrf_token %} +
+
Name
+ +
+
+
Surname
+ +
+
+
Username
+ + + +
+
+ +
+ +
+
+ + \ No newline at end of file diff --git a/frontend/iva/templates/iva/new_software.html b/frontend/iva/templates/iva/new_software.html new file mode 100644 index 0000000..d07f391 --- /dev/null +++ b/frontend/iva/templates/iva/new_software.html @@ -0,0 +1,56 @@ +{% extends "iva/main_template.html" %} +{% load staticfiles %} +{% block title %}New Software{% endblock %} +{% load custom_filters %} +{% block js %} + {% include "iva/jplist_includes.html" %} + +{% endblock %} +{% block navigation-bar %} > new software{% endblock %} +{% block content %} +
New Software
+
+ {% if read_inventory_error %} +
unable to read inventory from GLPI DB
+ {% endif %} +
+ read new software from inventory database + + + +
+
{% csrf_token %} +
+ search product(s) +
+ +
+
+ +
+
+
+
+ {% include 'iva/jplist_head.html' %} +


+ + + + + {% for item in items %} + + + + + + + {% endfor %} +
ProductVendorVersionOptions
{{ item.product }}{{ item.vendor }}{{ item.version }} + + + +
+ {% include 'iva/jplist_foot.html' %} +
+
+{% endblock %} \ No newline at end of file diff --git a/frontend/iva/templates/iva/search_cves.html b/frontend/iva/templates/iva/search_cves.html new file mode 100644 index 0000000..e09efdb --- /dev/null +++ b/frontend/iva/templates/iva/search_cves.html @@ -0,0 +1,139 @@ +{% extends "iva/main_template.html" %} +{% load staticfiles %} +{% load custom_filters %} +{% block title %}CVE Matches{% endblock %} +{% block js %} + {% include "iva/jplist_includes.html" %} + +{% endblock %} +{% block navigation-bar %} + > software with CPE + > CVE matches +{% endblock %} +{% block content %} + {% include "iva/sw_product.html" %} +
+
CVE Matches
+
+
+ {% include 'iva/jplist_head.html' %} +


+ {% csrf_token %} + + + + + {% for match in cve_matches %} + {% if match.positive == 0 and match.removed == 0 %} + + + + + + + + {% endif %} + {% endfor %} +
CVEOptions
+ {{ match.cve_id }} + {% include "iva/cve_match_cpe_entries.html" %} + +
+ +
+ +
+ + + + + + + +
+ {% include 'iva/jplist_foot.html' %} +
+
+
+
Confirmed matches
+
+
+ {% include 'iva/jplist_head.html' %} +


+ + + + + {% for match in cve_matches %} + {% if match.positive == 1 and match.removed == 0 %} + + + + + + + {% endif %} + {% endfor %} +
CVEOptions
+ {{ match.cve_id }} + {% include "iva/cve_match_cpe_entries.html" %} + +
+ +
+ +
+ + + + + +
+ {% include 'iva/jplist_foot.html' %} +
+
+
+
Removed matches
+
+
+ {% include 'iva/jplist_head.html' %} +


+ + + + + {% for match in cve_matches %} + {% if match.removed == 1 %} + + + + + + + {% endif %} + {% endfor %} +
CVEOptions
+ {{ match.cve_id }} + {% include "iva/cve_match_cpe_entries.html" %} + +
+ +
+ +
+ + + + + +
+ {% include 'iva/jplist_foot.html' %} +
+
+{% endblock %} diff --git a/frontend/iva/templates/iva/sign_in.html b/frontend/iva/templates/iva/sign_in.html new file mode 100644 index 0000000..2bb5ea3 --- /dev/null +++ b/frontend/iva/templates/iva/sign_in.html @@ -0,0 +1,31 @@ +{% load static %} + + + + + Sign in + + +
+
Sign in
+
+
{% csrf_token %} +
+
Username
+ +
+
+
Password
+ +
+
+ +
+
+
+
+ {% if not is_valid_user %} +
invalid user
+ {% endif %} + + diff --git a/frontend/iva/templates/iva/sw_product.html b/frontend/iva/templates/iva/sw_product.html new file mode 100644 index 0000000..783bd2d --- /dev/null +++ b/frontend/iva/templates/iva/sw_product.html @@ -0,0 +1,36 @@ +{% load static %} +{% load custom_filters %} +
+ Software Product +
+ + + The information (vendor, product, and version) shown in this box was obtained from the inventory database (e.g. GLPI). + +
+
+
+
+
Vendor
+
{{software.vendor}}
+
+
+
Product
+
{{software.product}}
+
+
+
Version
+
{{software.version}}
+
+ {% if software.cpe != None %} +
+
CPE
+
{{software.cpe.uri_binding | decode_uri_binding}}
+
+ + + +
+
+ {% endif %} +
\ No newline at end of file diff --git a/frontend/iva/templates/iva/sw_products_with_cpe.html b/frontend/iva/templates/iva/sw_products_with_cpe.html new file mode 100644 index 0000000..c9bcb39 --- /dev/null +++ b/frontend/iva/templates/iva/sw_products_with_cpe.html @@ -0,0 +1,56 @@ +{% extends "iva/main_template.html" %} +{% load staticfiles %} +{% block title %}CPEs{% endblock %} +{% block js %} + {% include "iva/jplist_includes.html" %} + +{% endblock %} +{% block navigation-bar %} + > software with CPE +{% endblock %} +{% block content %} +
Software with CPE
+
+
+ {% include 'iva/jplist_head.html' %} +


+ + + + + + + + + + + {% load custom_filters %} + {% for software in inventory %} + + + + + + + + + + {% endfor %} +
ProductVendorVersionCPEOptions
{{ software.product }} {{ software.vendor }} {{ software.version }} {{ software.cpe.uri_binding | decode_uri_binding }} + + + + + + + + + +
+ {% include 'iva/jplist_foot.html' %} +
+
+ {% if updated %} +
{{ updated | decode_uri_binding}} assigned to software
+ {% endif %} +{% endblock %} \ No newline at end of file diff --git a/frontend/iva/templates/iva/users.html b/frontend/iva/templates/iva/users.html new file mode 100644 index 0000000..96018cc --- /dev/null +++ b/frontend/iva/templates/iva/users.html @@ -0,0 +1,46 @@ +{% extends "iva/main_template.html" %} +{% load staticfiles %} +{% block title %} Users {% endblock %} +{% block css %}{% endblock %} +{% block js %} + + {% include "iva/jplist_includes.html" %} +{% endblock %} +{% block navigation-bar %} > Users {% endblock %} +{% block content %} +
+ Users + + + +
+
+ {% csrf_token %} + + + + + {% for user in users %} + + + + + + + {% endfor %} +
NameSurnameUsernameOptions
{{user.name}}{{user.surname}}{{user.username}} + + + + + + + + + +
+ {% if pwd_changed %} +
Password of user {{ username }} was updated successfully
+ {% endif %} +
+{% endblock %} \ No newline at end of file diff --git a/frontend/iva/templatetags/custom_filters.py b/frontend/iva/templatetags/custom_filters.py new file mode 100644 index 0000000..52d1634 --- /dev/null +++ b/frontend/iva/templatetags/custom_filters.py @@ -0,0 +1,17 @@ +from django import template +from django.template.defaultfilters import stringfilter +from urllib.parse import unquote + +register = template.Library() + + +@register.filter +@stringfilter +def decode_uri_binding(value): + return unquote(value) + + +@register.filter +@stringfilter +def get_two_digits_number(number): + return str(number).zfill(2) \ No newline at end of file diff --git a/frontend/iva/templatetags/custom_tags.py b/frontend/iva/templatetags/custom_tags.py new file mode 100644 index 0000000..12414f5 --- /dev/null +++ b/frontend/iva/templatetags/custom_tags.py @@ -0,0 +1,36 @@ +import pycountry +from django import template +from alerts.alerts import Alerts +from inventory.inventory import Inventory +from local_repositories.tasks.scheduler import TaskScheduler + +register = template.Library() + + +@register.simple_tag +def number_new_alerts(): + alerts = Alerts() + new_alerts = alerts.get_number_of_new_alerts() + sent_alerts = alerts.get_number_of_sent_alerts() + return new_alerts + sent_alerts + + +@register.simple_tag +def number_new_glpi_items(): + return len(Inventory().get_software_products_without_assigned_cpe()) + + +@register.simple_tag +def scheduled_tasks(): + return TaskScheduler().get_number_of_scheduled_tasks() + + +@register.simple_tag +def get_country_name(code): + try: + return pycountry.languages.get(iso639_1_code=code).name + except: + return code + + + diff --git a/frontend/iva/tests.py b/frontend/iva/tests.py new file mode 100644 index 0000000..d412727 --- /dev/null +++ b/frontend/iva/tests.py @@ -0,0 +1 @@ +#from django.test import TestCase diff --git a/frontend/iva/urls.py b/frontend/iva/urls.py new file mode 100644 index 0000000..4490d61 --- /dev/null +++ b/frontend/iva/urls.py @@ -0,0 +1,26 @@ +from django.conf.urls import url + +from . import views + +app_name = 'iva' +urlpatterns = [url(r'^index.html*', views.index, name='index'), + url(r'^assign_cpe.html*', views.assign_cpe, name='assign_cpe'), + url(r'^search_cves.html*', views.search_cves, name='search_cves'), + url(r'^cve_matches.html*', views.cve_matches, name='cve_matches'), + url(r'^new_software', views.new_software, name='new_software'), + url(r'^sw_products_with_cpe', views.sw_products_with_cpe, name='sw_products_with_cpe'), + url(r'^alerts', views.alerts, name='alerts'), + url(r'^cpe_wfn.html*', views.cpe_wfn, name='cpe_wfn'), + url(r'^alert_log.html*', views.alert_log, name='log'), + url(r'^alert_notes.html*', views.alert_notes, name='alert_notes'), + url(r'^alert_status.html*', views.alert_status, name='alert_status'), + url(r'^compare_cpe.html*', views.compare_cpe, name='compare_cpe'), + url(r'^modify_cpe.html*', views.modify_cpe, name='modify_cpe'), + url(r'^sign_in.html*', views.sign_in, name='sign_in'), + url(r'^users.html*', views.users, name='users'), + url(r'^add_user.html*', views.add_user, name='add_user'), + url(r'^change_user_pwd.html*', views.change_user_pwd, name='change_user_pwd'), + url(r'^modify_user.html*', views.modify_user, name='modify_user'), + url(r'^local_repositories.html*', views.local_repositories, name='local_repositories'), + url(r'^change_daily_db_update_time.html*', views.change_daily_db_update_time, name='change_daily_db_update_time'), + url(r'^grouped_cve_matches.html*', views.grouped_cve_matches, name='grouped_cve_matches')] diff --git a/frontend/iva/views.py b/frontend/iva/views.py new file mode 100644 index 0000000..6d4613d --- /dev/null +++ b/frontend/iva/views.py @@ -0,0 +1,153 @@ +import sys +import os + +path = os.path.abspath(os.path.join(os.path.dirname(__file__), '../..')) +if not path in sys.path: + sys.path.insert(1, path) + +from .handlers import assign_cpe_handler +from .handlers import search_cves_handler +from .handlers import index_handler +from .handlers import new_software_handler +from .handlers import sw_products_with_cpe_handler +from .handlers import alerts_handler +from .handlers import cpe_wfn_handler +from .handlers import compare_cpe_handler +from .handlers import modify_cpe_handler +from .handlers import alert_log_handler +from .handlers import alert_notes_handler +from .handlers import alert_status_handler +from .handlers import cve_matches_handler +from .handlers import sign_in_handler +from .handlers.sign_in_handler import USER_SESSION_KEY +from .handlers import users_handler +from .handlers import add_user_handler +from .handlers import change_user_pwd_handler +from .handlers import modify_user_handler +from .handlers import daily_db_update_handler +from .handlers import change_daily_db_update_time_handler +from .handlers import grouped_cve_matches_handler + + +def sign_in(request): + return sign_in_handler.handle_request(request) + + +def index(request): + if USER_SESSION_KEY in request.session: + return index_handler.handle_request(request) + return sign_in(request) + + +def assign_cpe(request): + if USER_SESSION_KEY in request.session: + return assign_cpe_handler.handle_request(request) + return sign_in(request) + + +def search_cves(request): + if USER_SESSION_KEY in request.session: + return search_cves_handler.handle_request(request) + return sign_in(request) + + +def cve_matches(request): + if USER_SESSION_KEY in request.session: + return cve_matches_handler.handle_request(request) + return sign_in(request) + + +def new_software(request): + if USER_SESSION_KEY in request.session: + return new_software_handler.handle_request(request) + return sign_in(request) + + +def sw_products_with_cpe(request): + if USER_SESSION_KEY in request.session: + return sw_products_with_cpe_handler.handle_request(request) + return sign_in(request) + + +def alerts(request): + if USER_SESSION_KEY in request.session: + return alerts_handler.handle_request(request) + return sign_in(request) + + +def cpe_wfn(request): + if USER_SESSION_KEY in request.session: + return cpe_wfn_handler.handle_request(request) + return sign_in(request) + + +def compare_cpe(request): + if USER_SESSION_KEY in request.session: + return compare_cpe_handler.handle_request(request) + return sign_in(request) + + +def modify_cpe(request): + if USER_SESSION_KEY in request.session: + return modify_cpe_handler.handle_request(request) + return sign_in(request) + + +def alert_log(request): + if USER_SESSION_KEY in request.session: + return alert_log_handler.handle_request(request) + return sign_in(request) + + +def alert_notes(request): + if USER_SESSION_KEY in request.session: + return alert_notes_handler.handle_request(request) + return sign_in(request) + + +def alert_status(request): + if USER_SESSION_KEY in request.session: + return alert_status_handler.handle_request(request) + return sign_in(request) + + +def users(request): + if USER_SESSION_KEY in request.session: + return users_handler.handle_request(request) + return sign_in(request) + + +def add_user(request): + if USER_SESSION_KEY in request.session: + return add_user_handler.handle_request(request) + return sign_in(request) + + +def change_user_pwd(request): + if USER_SESSION_KEY in request.session: + return change_user_pwd_handler.handle_request(request) + return sign_in(request) + + +def modify_user(request): + if USER_SESSION_KEY in request.session: + return modify_user_handler.handle_request(request) + return sign_in(request) + + +def local_repositories(request): + if USER_SESSION_KEY in request.session: + return daily_db_update_handler.handle_request(request) + return sign_in(request) + + +def change_daily_db_update_time(request): + if USER_SESSION_KEY in request.session: + return change_daily_db_update_time_handler.handle_request(request) + return sign_in(request) + + +def grouped_cve_matches(request): + if USER_SESSION_KEY in request.session: + return grouped_cve_matches_handler.handle_request(request) + return sign_in(request) \ No newline at end of file diff --git a/frontend/manage.py b/frontend/manage.py new file mode 100644 index 0000000..e3e1ad5 --- /dev/null +++ b/frontend/manage.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python +import os +import sys +from django.core.management import execute_from_command_line + +path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) +if not path in sys.path: + sys.path.insert(1, path) + +import config + + +def set_django_settings_env_var(): + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "frontend_env.settings") + + +def set_iva_config_file_env_var(): + if len(sys.argv) == 4: + os.environ.setdefault("IVA_CONFIG_FILE", sys.argv.pop()) + + +def load_iva_config(): + config.load_configuration_from_file(os.environ.get("IVA_CONFIG_FILE")) + + +if __name__ == "__main__": + set_django_settings_env_var() + set_iva_config_file_env_var() + load_iva_config() + execute_from_command_line(sys.argv) diff --git a/inventory/glpi_inventory.py b/inventory/glpi_inventory.py new file mode 100644 index 0000000..0690726 --- /dev/null +++ b/inventory/glpi_inventory.py @@ -0,0 +1,45 @@ +import traceback +from contextlib import closing +import pymysql.cursors +import config +import logger + +SQL_QUERY = "SELECT DISTINCT `glpi_softwares`.`name` AS product, `glpi_softwareversions`.`name` AS version, " \ + "`glpi_manufacturers`.`name` AS vendor " \ + "FROM `glpi_computers_softwareversions` " \ + "INNER JOIN `glpi_softwareversions` " \ + "ON (`glpi_computers_softwareversions`.`softwareversions_id` = `glpi_softwareversions`.`id`) " \ + "INNER JOIN `glpi_softwares` ON (`glpi_softwareversions`.`softwares_id` = `glpi_softwares`.`id`) " \ + "LEFT JOIN `glpi_manufacturers` " \ + "ON (`glpi_softwares`.`manufacturers_id` = `glpi_manufacturers`.`id`) " \ + "WHERE `glpi_computers_softwareversions`.`is_deleted_computer` = '0' " \ + "AND `glpi_computers_softwareversions`.`is_template_computer` = '0' " \ + "AND `glpi_computers_softwareversions`.`is_deleted` = '0' " \ + "AND `glpi_softwares`.`is_deleted` = '0' " \ + "AND `glpi_softwares`.`is_template` = '0' " \ + "ORDER BY product, version" + + +def read_inventory(): + logger.info('GLPI - reading inventory') + try: + with closing(connect_to_mysql_server()) as connection: + with connection.cursor() as cursor: + cursor.execute(SQL_QUERY) + return cursor.fetchall() + except: + logger.error('GLPI - unable to read inventory') + logger.error('MYSQL - ' + str(traceback.format_exc())) + raise + + +def connect_to_mysql_server(): + host = config.get_inventory_database_host() + db_name = config.get_glpi_db_name() + user = config.get_inventory_database_user() + pwd = config.get_inventory_database_password() + try: + return pymysql.connect(host=host, user=user, password=pwd, db=db_name, cursorclass=pymysql.cursors.DictCursor) + except Exception: + logger.error('MYSQL - failed to connect to DB ' + db_name + ' in HOST ' + host + ' with USER ' + user) + raise diff --git a/inventory/inventory.py b/inventory/inventory.py new file mode 100644 index 0000000..6ecff2b --- /dev/null +++ b/inventory/inventory.py @@ -0,0 +1,152 @@ +import hashlib +from database import Database +from inventory import inventory_reader + +INVENTORY_DB_COLLECTION = 'inventory' + + +class Inventory: + + def __init__(self): + self.db = Database() + + def insert_new_software_products_to_db(self): + for software in inventory_reader.read_inventory(): + self.insert_software_in_db(software) + + def insert_software_in_db(self, software): + software = Software(software) + if not self.exists_in_db(software): + self.db.insert_document_in_collection(software.get(), INVENTORY_DB_COLLECTION) + + def exists_in_db(self, software): + return self.db.exist_doc_in_collection({'id': software.get_id()}, INVENTORY_DB_COLLECTION) + + def search_software_products_without_assigned_cpe(self, search_term): + sw_products_without_cpe = self.get_software_products_without_assigned_cpe() + if search_term != '' and search_term is not None: + sw_products = [] + for sw in sw_products_without_cpe: + search_term = str(search_term).lower() + sw_product = str(sw.get('product')).lower() + sw_vendor = str(sw.get('vendor')).lower() + if (search_term in sw_product) or (search_term in sw_vendor): + sw_products.append(sw) + return sw_products + return sw_products_without_cpe + + def get_software_products_without_assigned_cpe(self): + products_without_cpe = [] + for software in self.get_inventory(): + if not has_cpe(software): + products_without_cpe.append(software) + return products_without_cpe + + def get_software_products_with_assigned_cpe(self): + products_with_cpe = [] + for software in self.get_inventory(): + if has_cpe(software): + products_with_cpe.append(software) + return products_with_cpe + + def get_inventory(self): + return sort_software_products_by_vendor(list(self.db.get_documents_from_collection(INVENTORY_DB_COLLECTION))) + + def get_software_by_id(self, software_id): + return self.db.search_document_in_collection({'id': software_id}, INVENTORY_DB_COLLECTION) + + def get_vendors(self): + return self.get_wfn_values_for_attribute('vendor') + + def get_products(self): + return self.get_wfn_values_for_attribute('product') + + def get_vendor_products(self, vendor): + products = [] + for software in self.get_inventory(): + cpe = software.get('cpe') + if cpe is not None: + wfn = cpe.get('wfn') + v = wfn.get('vendor') + p = wfn.get('product') + if v == vendor and p not in products: + products.append(p) + return products + + def get_wfn_values_for_attribute(self, attribute): + values = [] + for software in self.get_inventory(): + cpe = software.get('cpe') + if cpe is not None: + value = cpe.get('wfn').get(attribute) + if value not in values: + values.append(value) + return values + + +class Software: + + def __init__(self, new_software): + self.new_software = new_software + self.update_software_document() + + def get(self): + return self.new_software + + def get_id(self): + return self.new_software.get('id') + + def update_software_document(self): + self.new_software['id'] = generate_software_id(self.new_software) + self.new_software['cpe'] = None + self.new_software['cve_matches'] = [] + + +def sort_software_products_by_vendor(sw_products): + ordered_sw_products = [] + none = [] + ubuntu = [] + others = [] + microsoft = [] + for software in sw_products: + if is_software_vendor_microsoft(software): + microsoft.append(software) + elif is_software_vendor_ubuntu(software): + ubuntu.append(software) + elif is_software_vendor_none(software): + none.append(software) + else: + others.append(software) + ordered_sw_products.extend(microsoft) + ordered_sw_products.extend(others) + ordered_sw_products.extend(ubuntu) + ordered_sw_products.extend(none) + return ordered_sw_products + + +def is_software_vendor_microsoft(software): + return 'microsoft' in str.lower(str(software.get('vendor'))) + + +def is_software_vendor_ubuntu(software): + return 'ubuntu' in str.lower(str(software.get('vendor'))) + + +def is_software_vendor_none(software): + return software.get('vendor') is None + + +def generate_software_id(software): + return generate_md5(generate_software_id_seed(software)) + + +def generate_md5(seed): + return hashlib.md5(str.encode(seed)).hexdigest() + + +def generate_software_id_seed(item): + return str(item.get('product')) + str(item.get('vendor')) + str(item.get('version')) + + +def has_cpe(software): + return software.get('cpe') is not None diff --git a/inventory/inventory_reader.py b/inventory/inventory_reader.py new file mode 100644 index 0000000..8bd91ab --- /dev/null +++ b/inventory/inventory_reader.py @@ -0,0 +1,20 @@ +import ast +from inventory import glpi_inventory + + +def read_inventory(file_path=None): + if file_path is not None: + return read_inventory_from_file(file_path) + return glpi_inventory.read_inventory() + + +def read_inventory_from_file(file_path): + with open(file_path, 'r', encoding='utf8') as f: + return convert_list_elements_to_dict(f.readlines()) + + +def convert_list_elements_to_dict(str_list): + dict_list = [] + for s in str_list: + dict_list.append(ast.literal_eval(s)) + return dict_list diff --git a/inventory/software_cpe.py b/inventory/software_cpe.py new file mode 100644 index 0000000..f12a1a0 --- /dev/null +++ b/inventory/software_cpe.py @@ -0,0 +1,46 @@ +from alerts.alerts import Alerts +from database import Database +from inventory.inventory import INVENTORY_DB_COLLECTION +from wfn.wfn_converter import WFNConverter + + +class SoftwareCPE: + + def __init__(self): + self.db = Database() + self.alerts = Alerts() + + def create_sw_cpe_dict(self, wfn): + return {'wfn': wfn, 'uri_binding': bind_wfn_to_uri(wfn)} + + def assign_cpe_to_software(self, cpe, software_id): + self.db.update_document_in_collection({'id': software_id}, {'cpe': cpe}, INVENTORY_DB_COLLECTION) + + def update_software_cpe(self, software_id, new_wfn): + self.update(software_id, bind_wfn_to_uri(new_wfn), new_wfn) + self.alerts.change_alert_status(software_id, 'removed') + + def update(self, software_id, new_uri, new_wfn): + self.db.update_document_in_collection({'id': software_id}, + {'cpe': {'uri_binding': new_uri, 'wfn': new_wfn}, 'cve_matches': []}, + INVENTORY_DB_COLLECTION) + + def get_software_cpe_by_id(self, software_id): + software = self.get_software({'id': software_id}) + return get_cpe_from_software_dict(software) + + def get_software_cpe_by_uri(self, uri_binding): + software = self.get_software({'cpe.uri_binding': uri_binding}) + return get_cpe_from_software_dict(software) + + def get_software(self, search_condition): + return self.db.search_document_in_collection(search_condition, INVENTORY_DB_COLLECTION) + + +def bind_wfn_to_uri(wfn): + return WFNConverter().convert_wfn_to_uri(wfn) + + +def get_cpe_from_software_dict(software): + if software is not None: + return software.get('cpe') \ No newline at end of file diff --git a/inventory/software_cve_matches.py b/inventory/software_cve_matches.py new file mode 100644 index 0000000..79310d9 --- /dev/null +++ b/inventory/software_cve_matches.py @@ -0,0 +1,265 @@ +from database import Database +from alerts.alerts import Alerts +from inventory.inventory import INVENTORY_DB_COLLECTION, Inventory +from wfn.wfn_converter import WFNConverter +from wfn.wfn_comparator import versions_equal + + +class CVEMatches: + + def __init__(self): + self.inventory = Inventory() + self.alerts = Alerts() + self.db = Database() + + def insert_software_cve_matches(self, software_id, matches): + new_matches = [] + matches = format_matches(matches) + for old_match in self.get_software_cve_matches(software_id): + is_match = False + for match in matches: + if old_match.get('cve_id') == match.get('cve_id'): + new_matches.append(old_match) + matches.remove(match) + is_match = True + break + if not is_match: + self.remove_old_cve_match_from_alert(old_match, software_id) + new_matches.extend(matches) + sw = self.inventory.get_software_by_id(software_id) + self.update_matches_in_db(software_id, sort_cve_matches_by_version(new_matches, get_sw_version(sw))) + + def remove_old_cve_match_from_alert(self, old_match, software_id): + if is_cve_match_positive(old_match): + self.alerts.remove_cve_from_alert(software_id, old_match.get('cve_id')) + + def add_new_cve_match_to_software(self, software_id, new_match): + cve_matches = self.get_software_cve_matches(software_id) + self.update_matches_in_db(software_id, add_new_match_to_list(cve_matches, format_match(new_match))) + + def set_cve_matches_group_as_removed(self, software_id, cve_id_master): + group = self.get_software_cve_matches_with_same_cpe_entries_as_cve(software_id, cve_id_master) + for match in group: + self.set_cve_match_as_removed(software_id, match.get('cve_id')) + + def set_cve_match_as_removed(self, software_id, cve_id): + self.update_cve_match_status(software_id, cve_id, 'removed', 1) + + def restore_cve_matches_group(self, software_id, cve_id_master): + group = self.get_software_cve_matches_with_same_cpe_entries_as_cve(software_id, cve_id_master) + for match in group: + self.restore_cve_match(software_id, match.get('cve_id')) + + def restore_cve_match(self, software_id, cve_id): + self.update_cve_match_status(software_id, cve_id, 'removed', 0) + + def set_cve_matches_group_as_positive(self, software_id, cve_id_master): + group = self.get_software_cve_matches_with_same_cpe_entries_as_cve(software_id, cve_id_master) + for match in group: + self.set_cve_match_as_positive(software_id, match.get('cve_id')) + + def set_cve_match_as_positive(self, software_id, cve_id): + self.update_cve_match_status(software_id, cve_id, 'positive', 1) + self.alerts.insert_alert(software_id, cve_id) + + def set_cve_matches_group_as_negative(self, software_id, cve_id_master): + group = self.get_software_cve_matches_with_same_cpe_entries_as_cve(software_id, cve_id_master) + for match in group: + self.set_cve_match_as_negative(software_id, match.get('cve_id')) + + def set_cve_match_as_negative(self, software_id, cve_id): + self.update_cve_match_status(software_id, cve_id, 'positive', 0) + self.alerts.remove_cve_from_alert(software_id, cve_id) + + def update_cve_match_status(self, software_id, cve_id, field, status): + matches = self.get_software_cve_matches(software_id) + for match in matches: + if match.get('cve_id') == cve_id: + match.update({field: status}) + self.update_matches_in_db(software_id, matches) + break + + def get_software_cve_matches_with_same_cpe_entries_as_cve(self, software_id, cve_id): + cve_matches = self.get_software_cve_matches(software_id) + cve_match_a = get_cve_match_from_matches(cve_id, cve_matches) + matches_with_same_cpes = [] + for cve_match_b in cve_matches: + if equal_removed_and_positive_status(cve_match_a, cve_match_b): + if equal_cpe_entries(get_cpe_entries_from_cve(cve_match_a), get_cpe_entries_from_cve(cve_match_b)): + matches_with_same_cpes.append(cve_match_b) + return matches_with_same_cpes + + def get_software_cve_matches(self, software_id): + software = self.get_software(software_id) + return get_sw_cve_matches(software) + + def get_vulnerable_software_items(self): + vulnerable_software = [] + for software in self.get_software_items(): + if is_vulnerable(software): + vulnerable_software.append(software) + return vulnerable_software + + def exist_cve_matches_for_software(self, software_id): + return len(self.get_software_cve_matches(software_id)) > 0 + + def get_software_items(self): + return self.db.get_documents_from_collection(INVENTORY_DB_COLLECTION) + + def remove_software_cve_matches(self, software_id): + self.update_matches_in_db(software_id, []) + + def update_matches_in_db(self, software_id, matches): + self.db.update_document_in_collection({'id': software_id}, {'cve_matches': matches}, INVENTORY_DB_COLLECTION) + + def get_software(self, software_id): + return self.db.search_document_in_collection({'id': software_id}, INVENTORY_DB_COLLECTION) + + def get_cve_matches(self, filters=None): + cve_matches = [] + for software in self.inventory.get_inventory(): + append_software_matches_to_list(software, cve_matches) + return filter_matches(filters, cve_matches) + + def get_vendor_product_cve_matches(self, vendor='all', product='all', filters=None): + if vendor != 'all': + cve_matches = [] + for software in self.inventory.get_inventory(): + if is_vendor_and_product(vendor, product, software): + append_software_matches_to_list(software, cve_matches) + return filter_matches(filters, cve_matches) + return self.get_cve_matches(filters) + + +def sort_cve_matches_by_version(cve_matches, version): + return sorted(cve_matches, key=lambda cve: has_cpe_with_equal_version(cve, version), reverse=True) + + +def has_cpe_with_equal_version(cve_match, version): + cpe_entries = get_cpe_entries_from_cve(cve_match) + for uri in cpe_entries: + if versions_equal(get_version_from_uri(uri), version): + return True + return False + + +def format_matches(matches): + formatted_matches = [] + for match in matches: + formatted_matches.append(format_match(match)) + return formatted_matches + + +def format_match(match): + del match['cve_summary'] + match['cpe_entries'] = get_uri_bindings_from_cpes(match.get('cpe_entries')) + match['positive'] = 0 + match['removed'] = 0 + return match + + +def get_uri_bindings_from_cpes(cpe_entries): + uri_bindings = [] + for cpe in cpe_entries: + uri_bindings.append(cpe.get('uri_binding')) + return uri_bindings + + +def add_new_match_to_list(matches, new_match): + matches.append(new_match) + return matches + + +def is_vulnerable(software): + for match in get_sw_cve_matches(software): + if is_cve_match_positive(match): + return True + return False + + +def append_software_matches_to_list(software, list_): + for match in get_sw_cve_matches(software): + list_.append(create_sw_cve_match(match, software)) + del software['cve_matches'] + + +def create_sw_cve_match(match, software): + return {'cve_id': match.get('cve_id'), 'cpe_entries': match.get('cpe_entries'), + 'removed': match.get('removed'), 'positive': match.get('positive'), 'software': software} + + +def filter_matches(filters, matches): + if filters is not None: + if 'hide_removed' in filters: + matches = discard_removed_matches(matches) + if 'ordered_by_year_desc' in filters: + matches = sorted(matches, key=lambda k: k['cve_id'], reverse=True) + if 'ordered_by_year_asc' in filters: + matches = sorted(matches, key=lambda k: k['cve_id'], reverse=False) + return matches + + +def discard_removed_and_update_matches(software): + software.update({'cve_matches': discard_removed_matches(get_sw_cve_matches(software))}) + + +def discard_removed_matches(matches): + not_removed_matches = [] + for match in matches: + if not is_cve_match_removed(match): + not_removed_matches.append(match) + return not_removed_matches + + +def is_vendor_and_product(vendor, product, software): + if software.get('cpe') is not None: + if vendor == get_sw_vendor(software) and (product == 'all' or product == get_sw_product(software)): + return True + return False + + +def equal_cpe_entries(cpe_entries_a, cpe_entries_b): + return set(cpe_entries_a) == set(cpe_entries_b) + + +def get_cve_match_from_matches(cve_id, cve_matches): + for match in cve_matches: + if match.get('cve_id') == cve_id: + return match + + +def equal_removed_and_positive_status(cve_match_a, cve_match_b): + return cve_match_a.get('removed') == cve_match_b.get('removed') and \ + cve_match_a.get('positive') == cve_match_b.get('positive') + + +def is_cve_match_positive(match): + return match.get('positive') == 1 + + +def is_cve_match_removed(match): + return match.get('removed') == 1 + + +def get_sw_product(software): + return software.get('cpe').get('wfn').get('product') + + +def get_sw_vendor(software): + return software.get('cpe').get('wfn').get('vendor') + + +def get_sw_version(software): + return software.get('cpe').get('wfn').get('version') + + +def get_sw_cve_matches(software): + return software.get('cve_matches') + + +def get_cpe_entries_from_cve(cve_match): + return cve_match.get('cpe_entries') + + +def get_version_from_uri(uri): + return WFNConverter().convert_cpe_uri_to_wfn(uri).get('version') \ No newline at end of file diff --git a/local_repositories/cpe_dict.py b/local_repositories/cpe_dict.py new file mode 100644 index 0000000..86a4827 --- /dev/null +++ b/local_repositories/cpe_dict.py @@ -0,0 +1,26 @@ +from database import Database +from local_repositories.cve_search import CVESearchDB +from local_repositories import iva_formatter + +IVA_CPE_COLLECTION = 'cpes' + + +class CPEDict: + + def __init__(self): + self.iva_db = Database() + self.cve_search_db = CVESearchDB() + + def update_cpe_dict(self): + cpe_dict = [] + for cpe in self.cve_search_db.get_cpe_dict(): + formatted_cpe = iva_formatter.format_cpe(cpe) + cpe_dict.append(formatted_cpe) + self.update(cpe_dict) + self.iva_db.close() + + def update(self, new_cpe_dict): + self.iva_db.update_documents_in_collection(new_cpe_dict, iva_formatter.IVA_CPE_KEYS.uri, IVA_CPE_COLLECTION) + + def number_of_entries(self): + return self.iva_db.get_number_of_documents_in_collection(IVA_CPE_COLLECTION) diff --git a/local_repositories/cve_feeds.py b/local_repositories/cve_feeds.py new file mode 100644 index 0000000..3eb12e3 --- /dev/null +++ b/local_repositories/cve_feeds.py @@ -0,0 +1,47 @@ +from database import Database +from local_repositories.cve_search import CVESearchDB +from local_repositories import iva_formatter + +IVA_CVE_COLLECTION = 'cves' +BATCH_SIZE = 4000 + + +class CVEFeeds: + + def __init__(self): + self.iva_db = Database() + self.cve_search_db = CVESearchDB() + + def update_cve_feeds(self): + self.drop_cve_feeds() + skip = 0 + number_cves_to_update = self.cve_search_db.get_number_of_cves_entries() + while number_cves_to_update > BATCH_SIZE: + self.update_cve_feeds_in_range(skip, BATCH_SIZE) + skip += BATCH_SIZE + number_cves_to_update -= BATCH_SIZE + if number_cves_to_update > 0: + self.update_cve_feeds_in_range(skip, 0) + self.close() + + def update_cve_feeds_in_range(self, skip, limit): + cve_feeds = [] + cves = self.cve_search_db.get_cve_feeds(skip, limit) + number_cves = len(cves) + for i in range(number_cves): + cve_feeds.append(iva_formatter.format_cve(cves.pop())) + del cves + self.update(cve_feeds) + + def update(self, new_cve_entries): + self.iva_db.insert_documents_in_collection(new_cve_entries, IVA_CVE_COLLECTION) + + def drop_cve_feeds(self): + self.iva_db.drop_collection(IVA_CVE_COLLECTION) + + def number_of_entries(self): + return self.iva_db.get_number_of_documents_in_collection(IVA_CVE_COLLECTION) + + def close(self): + self.iva_db.close() + self.cve_search_db.close() diff --git a/local_repositories/cve_search.py b/local_repositories/cve_search.py new file mode 100644 index 0000000..2b38537 --- /dev/null +++ b/local_repositories/cve_search.py @@ -0,0 +1,66 @@ +import os +import config +from database import Database + +DOWNLOADER_FILE = 'sbin/db_mgmt.py' +CPE_DICT_DOWNLOADER_FILE = 'sbin/db_mgmt_cpe_dictionary.py' +UPDATER_FILE = 'sbin/db_updater.py' +CVE_SEARCH_CPE_COLLECTION = 'cpe' +CVE_SEARCH_CVE_COLLECTION = 'cves' + + +class CVESearchDB: + + def __init__(self): + self.cve_search_db = Database(config.get_cve_search_db_name()) + + def get_cpe_dict(self): + return self.cve_search_db.get_documents_from_collection(CVE_SEARCH_CPE_COLLECTION) + + def get_cve_feeds(self, skip=0, limit=0): + return self.cve_search_db.get_documents_from_collection_in_range(CVE_SEARCH_CVE_COLLECTION, skip, limit) + + def get_number_of_cves_entries(self): + return self.cve_search_db.get_number_of_documents_in_collection(CVE_SEARCH_CVE_COLLECTION) + + def get_number_of_cpes_entries(self): + return self.cve_search_db.get_number_of_documents_in_collection(CVE_SEARCH_CPE_COLLECTION) + + def is_cve_search_populated(self): + return self.get_number_of_cpes_entries() > 0 and self.get_number_of_cves_entries() > 0 + + def close(self): + self.cve_search_db.close() + + +def populate_db(): + execute_file(get_downloader_file() + ' -p') + execute_file(get_cpe_dict_downloader_file()) + + +def update_db(): + execute_file(get_updater_file() + ' -c') + + +def repopulate_db(): + execute_file(get_updater_file() + ' -v -f') + + +def execute_file(file_): + os.system('python3 ' + file_) + + +def get_downloader_file(): + return join_(DOWNLOADER_FILE) + + +def get_cpe_dict_downloader_file(): + return join_(CPE_DICT_DOWNLOADER_FILE) + + +def get_updater_file(): + return join_(UPDATER_FILE) + + +def join_(file_): + return os.path.join(config.get_cve_search_dir(), file_) diff --git a/local_repositories/iva_formatter.py b/local_repositories/iva_formatter.py new file mode 100644 index 0000000..0a3b701 --- /dev/null +++ b/local_repositories/iva_formatter.py @@ -0,0 +1,42 @@ +from collections import namedtuple +from wfn.wfn_converter import WFNConverter + +CPE_KEYS = namedtuple('CPE_KEYS', ['uri', 'formatted_string'])('cpe_2_2', 'id') +IVA_CPE_KEYS = namedtuple('IVA_CPE_KEYS', ['wfn', 'uri', 'formatted_string'])('wfn', 'uri_binding', 'formatted_string_binding') + +CVE_KEYS = namedtuple('CVE_KEYS', ['id', 'summary', 'cpes'])('id', 'summary', 'vulnerable_configuration_cpe_2_2') +IVA_CVE_KEYS = namedtuple('IVA_CVE_KEYS', ['id', 'summary', 'cpes'])('cve_id', 'cve_summary', 'cpe_entries') + + +def format_cve(cve): + return {IVA_CVE_KEYS.id: cve.get(CVE_KEYS.id), + IVA_CVE_KEYS.summary: cve.get(CVE_KEYS.summary), + IVA_CVE_KEYS.cpes: get_cpe_entries(cve)} + + +def format_cpe(cpe): + uri = get_uri(cpe) + return {IVA_CPE_KEYS.uri: uri, IVA_CPE_KEYS.wfn: to_wfn(uri), IVA_CPE_KEYS.formatted_string: get_formatted_string(cpe)} + + +def get_cpe_entries(cve): + cpe_entries = [] + for uri_binding in cve.get(CVE_KEYS.cpes): + cpe_entries.append(create_cpe_doc(uri_binding)) + return cpe_entries + + +def create_cpe_doc(uri_binding): + return {IVA_CPE_KEYS.uri: uri_binding, IVA_CPE_KEYS.wfn: to_wfn(uri_binding)} + + +def get_uri(cpe_doc): + return cpe_doc.get(CPE_KEYS.uri) + + +def get_formatted_string(cpe_doc): + return cpe_doc.get(CPE_KEYS.formatted_string) + + +def to_wfn(uri_binding): + return WFNConverter().convert_cpe_uri_to_wfn(uri_binding) \ No newline at end of file diff --git a/local_repositories/tasks/datetime_utils.py b/local_repositories/tasks/datetime_utils.py new file mode 100644 index 0000000..3e8c56a --- /dev/null +++ b/local_repositories/tasks/datetime_utils.py @@ -0,0 +1,53 @@ +from datetime import datetime, timedelta + +TIME_FORMAT = '%H:%M:%S' + + +def calculate_task_execution_timeout(task_time): + current_datetime = datetime.now() + current_time = get_time_from_datetime(current_datetime) + return calculate_delta_time(task_time, current_time) + + +def calculate_task_next_execution_datetime(task_time): + current_datetime = get_current_datetime() + current_time = get_time_from_datetime(current_datetime) + if get_time_object(current_time) >= get_time_object(task_time): + current_datetime = add_one_day(current_datetime) + return update_time_in_datetime(current_datetime, task_time) + + +def get_current_datetime(): + return datetime.now() + + +def calculate_delta_time(time_a_str, time_b_str): + delta_time = (get_time_object(time_a_str) - get_time_object(time_b_str)).seconds + if delta_time > 0: + return delta_time + return 60 + + +def get_time_object(time_a): + return datetime.strptime(time_a, TIME_FORMAT) + + +def get_time_from_datetime(datetime_): + return datetime_.strftime(TIME_FORMAT) + + +def verify_time_format(time_str): + try: + datetime.strptime(time_str, TIME_FORMAT) + return True + except ValueError: + return False + + +def update_time_in_datetime(datetime_, time_str): + time_object = get_time_object(time_str) + return datetime_.replace(hour=time_object.hour, minute=time_object.minute, second=time_object.second) + + +def add_one_day(datetime_): + return datetime_ + timedelta(days=1) \ No newline at end of file diff --git a/local_repositories/tasks/operations.py b/local_repositories/tasks/operations.py new file mode 100644 index 0000000..ec7a8f5 --- /dev/null +++ b/local_repositories/tasks/operations.py @@ -0,0 +1,104 @@ +import traceback +import logger +from inventory.inventory import Inventory +from inventory.software_cve_matches import CVEMatches +from local_repositories import cve_search +from local_repositories.cpe_dict import CPEDict +from local_repositories.cve_feeds import CVEFeeds +from matching.cve_matcher import CVEMatcher + + +def update_db(): + logger.info('DB UPDATE - starting daily update') + update_cve_search_db() + update_iva_db() + search_cves() + logger.info('DB UPDATE - daily update finished') + + +def update_cve_search_db(): + logger.info('DB UPDATE - updating cve-search DB') + try: + cve_search.update_db() + logger.info('DB UPDATE - cve-search DB update finished') + except Exception: + logger.info('DB UPDATE - failed to update cve-search DB') + log_traceback() + + +def update_iva_db(): + logger.info('DB UPDATE - update IVA DB') + update_cpe_dict() + update_cve_feeds() + logger.info('DB UPDATE - update IVA DB finished') + + +def update_cpe_dict(): + logger.info('DB UPDATE - updating CPE dictionary') + try: + CPEDict().update_cpe_dict() + logger.info('DB UPDATE - updating CPE dictionary finished') + except Exception: + logger.info('DB UPDATE - failed to update CPE dictionary') + log_traceback() + + +def update_cve_feeds(): + logger.info('DB UPDATE - updating CVE feeds') + try: + CVEFeeds().update_cve_feeds() + logger.info('DB UPDATE - updating CVE feeds finished') + except Exception: + logger.info('DB UPDATE - failed to update CVE feeds') + log_traceback() + + +def search_cves(): + logger.info('DB UPDATE - search CVE matches for software products') + search_cves_and_update_db() + logger.info('DB UPDATE - search CVE matches finished') + + +def populate_db(): + logger.info('DB UPDATE - populating cve-search DB') + try: + cve_search.populate_db() + logger.info('DB UPDATE - populating cve-search finished') + except Exception: + logger.error('DB UPDATE - failed to populate cve-search DB') + log_traceback() + + +def repopulate_db(): + logger.info('DB UPDATE - repopulating cve-search DB') + try: + cve_search.repopulate_db() + logger.info('DB UPDATE - repopulating cve-search finished') + except Exception: + logger.error('DB UPDATE - failed to repopulate cve-search DB') + log_traceback() + + +def search_cves_and_update_db(): + for software in get_software_products(): + update_software_cve_matches(software) + + +def get_software_products(): + return Inventory().get_software_products_with_assigned_cpe() + + +def update_software_cve_matches(software): + CVEMatches().insert_software_cve_matches(software.get('id'), get_cve_matches(software)) + + +def get_cve_matches(software): + return CVEMatcher().search_cves_for_cpe(get_uri_binding(software)) + + +def get_uri_binding(software): + return software.get('cpe').get('uri_binding') + + +def log_traceback(): + logger.error('DB UPDATE - ' + str(traceback.format_exc())) \ No newline at end of file diff --git a/local_repositories/tasks/scheduler.py b/local_repositories/tasks/scheduler.py new file mode 100644 index 0000000..d59c061 --- /dev/null +++ b/local_repositories/tasks/scheduler.py @@ -0,0 +1,97 @@ +from threading import Timer +from local_repositories.tasks import datetime_utils as time_utils + + +class TaskScheduler(object): + + class __SingletonTaskScheduler: + + def __init__(self): + self.scheduled_tasks = {} + self.running_tasks = [] + + def schedule_task(self, task): + if task.is_task_active(): + update_next_time_execution(task) + timer = self.start_task_timer(task) + self.scheduled_tasks.update({task.name: timer}) + + def schedule_task_now(self, task): + self.scheduled_tasks.update({task.name: self.start_task_now(task)}) + + def start_task_timer(self, task): + timer = Timer(get_task_timeout(task), self.execute_task, [task]) + timer.start() + return timer + + def start_task_now(self, task): + timer = Timer(1, self.execute_task_only_once, [task]) + timer.start() + return timer + + def execute_task(self, task): + self.execute_task_only_once(task) + update_last_time_execution(task) + self.reschedule_task(task) + + def execute_task_only_once(self, task): + self.running_tasks.append(task.name) + task.execute() + self.running_tasks.remove(task.name) + + def reschedule_task(self, task): + self.remove_task_from_scheduled_tasks_list(task) + self.schedule_task(task) + + def is_task_scheduled(self, task_name): + return task_name in self.scheduled_tasks + + def is_task_running(self, task_name): + return task_name in self.running_tasks + + def cancel_task(self, task): + if task.name in self.scheduled_tasks: + timer = self.get_task_timer(task) + timer.cancel() + + def get_number_of_scheduled_tasks(self): + return len(self.scheduled_tasks) + + def remove_task_from_scheduled_tasks_list(self, task): + if task.name in self.scheduled_tasks: + del self.scheduled_tasks[task.name] + + def get_task_timer(self, task): + return self.scheduled_tasks.get(task.name) + + __instance = None + + def __new__(cls): + if not TaskScheduler.__instance: + TaskScheduler.__instance = TaskScheduler.__SingletonTaskScheduler() + return TaskScheduler.__instance + + def __getattr__(self, attr): + """ Delegate access to implementation """ + return getattr(self.__instance, attr) + + def __setattr__(self, attr, value): + """ Delegate access to implementation """ + return setattr(self.__instance, attr, value) + + +def get_task_timeout(task): + return time_utils.calculate_task_execution_timeout(task.get_execution_time()) + + +def update_next_time_execution(task): + task.update_next_execution(get_task_next_execution(task)) + + +def get_task_next_execution(task): + return time_utils.calculate_task_next_execution_datetime(task.get_execution_time()) + + +def update_last_time_execution(task): + task.update_last_execution(time_utils.get_current_datetime()) + diff --git a/local_repositories/tasks/task_info.py b/local_repositories/tasks/task_info.py new file mode 100644 index 0000000..5757234 --- /dev/null +++ b/local_repositories/tasks/task_info.py @@ -0,0 +1,66 @@ +from collections import namedtuple +from database import Database +from local_repositories.tasks import datetime_utils + + +TASKS_DB_COLLECTION = 'tasks' +DEFAULT_EXECUTION_TIME = '00:00:00' +STATUS = namedtuple('Status', ['active', 'inactive'])('active', 'inactive') +TASK = namedtuple('DictKeys', ['name', 'status', 'execution_time', 'next_execution', 'last_execution'])\ + ('name', 'status', 'execution_time', 'next_execution', 'last_execution') + + +class TaskInfo: + + def __init__(self): + self.name = None + self.db = Database() + + def create_task_in_db(self): + if not self.task_exists_in_db(): + self.db.insert_document_in_collection(create_task_dict(self.name), TASKS_DB_COLLECTION) + + def task_exists_in_db(self): + return self.db.exist_doc_in_collection({TASK.name: self.name}, TASKS_DB_COLLECTION) + + def update_execution_time(self, new_time): + if datetime_utils.verify_time_format(new_time): + self.db.update_document_in_collection({TASK.name: self.name}, {TASK.execution_time: new_time}, + TASKS_DB_COLLECTION) + self.update_next_execution(self.get_next_execution_datetime()) + + def activate_task(self): + self.update_next_execution(self.get_next_execution_datetime()) + self.db.update_document_in_collection({TASK.name: self.name}, {TASK.status: STATUS.active}, + TASKS_DB_COLLECTION) + + def deactivate_task(self): + self.update_next_execution('') + self.db.update_document_in_collection({TASK.name: self.name}, {TASK.status: STATUS.inactive}, + TASKS_DB_COLLECTION) + + def get_next_execution_datetime(self): + return datetime_utils.calculate_task_next_execution_datetime(self.get_task_info().get(TASK.execution_time)) + + def update_next_execution(self, datetime_): + self.db.update_document_in_collection({TASK.name: self.name}, {TASK.next_execution: datetime_}, + TASKS_DB_COLLECTION) + + def update_last_execution(self, datetime_): + self.db.update_document_in_collection({TASK.name: self.name}, {TASK.last_execution: datetime_}, + TASKS_DB_COLLECTION) + + def get_task_info(self): + return self.db.search_document_in_collection({TASK.name: self.name}, TASKS_DB_COLLECTION) + + def get_execution_time(self): + task_info = self.get_task_info() + return task_info.get(TASK.execution_time) + + def is_task_active(self): + return self.db.exist_doc_in_collection({TASK.name: self.name, TASK.status: STATUS.active}, TASKS_DB_COLLECTION) + + +def create_task_dict(task_name): + return {TASK.name: task_name, TASK.status: STATUS.inactive, + TASK.execution_time: DEFAULT_EXECUTION_TIME, TASK.last_execution: '', TASK.next_execution: ''} \ No newline at end of file diff --git a/local_repositories/tasks/task_populate_db.py b/local_repositories/tasks/task_populate_db.py new file mode 100644 index 0000000..7ec736e --- /dev/null +++ b/local_repositories/tasks/task_populate_db.py @@ -0,0 +1,11 @@ +from local_repositories.tasks import operations + + +class PopulateDB: + + def __init__(self): + self.name = 'populate_db' + + def execute(self): + operations.populate_db() + operations.update_iva_db() diff --git a/local_repositories/tasks/task_repopulate_db.py b/local_repositories/tasks/task_repopulate_db.py new file mode 100644 index 0000000..5a72905 --- /dev/null +++ b/local_repositories/tasks/task_repopulate_db.py @@ -0,0 +1,11 @@ +from local_repositories.tasks import operations + + +class RepopulateDB: + + def __init__(self): + self.name = 'repopulate_db' + + def execute(self): + operations.repopulate_db() + operations.update_iva_db() \ No newline at end of file diff --git a/local_repositories/tasks/task_update_db.py b/local_repositories/tasks/task_update_db.py new file mode 100644 index 0000000..8c0f8bb --- /dev/null +++ b/local_repositories/tasks/task_update_db.py @@ -0,0 +1,12 @@ +from local_repositories.tasks.task_info import TaskInfo +from local_repositories.tasks import operations + + +class DailyUpdate(TaskInfo): + + def __init__(self): + super().__init__() + self.name = 'update_db' + + def execute(self): + operations.update_db() diff --git a/logger.py b/logger.py new file mode 100644 index 0000000..3c0102f --- /dev/null +++ b/logger.py @@ -0,0 +1,49 @@ +import logging +import os + +ROOT_DIR = os.path.dirname(os.path.realpath(__file__)) +LOG_FILE = ROOT_DIR + '/iva.log' +LOGGER_FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' +logger = logging.getLogger('IVA') + + +def info(message): + logger.info(message) + + +def error(message): + logger.error(message) + + +def warning(message): + logger.warning(message) + + +def configure_logger(): + formatter = create_logging_formatter() + logger.setLevel(logging.DEBUG) + add_file_handler(formatter) + add_console_handler(formatter) + + +def create_logging_formatter(): + return logging.Formatter(LOGGER_FORMAT) + + +def add_file_handler(formatter): + fh = logging.FileHandler(LOG_FILE) + fh.setLevel(logging.DEBUG) + fh.setFormatter(formatter) + logger.addHandler(fh) + + +def add_console_handler(formatter): + ch = logging.StreamHandler() + ch.setLevel(logging.DEBUG) + ch.setFormatter(formatter) + logger.addHandler(ch) + + +configure_logger() + + diff --git a/main.py b/main.py new file mode 100644 index 0000000..ce2011a --- /dev/null +++ b/main.py @@ -0,0 +1,59 @@ +# Inventory Vulnerability Analysis (IVA) +# +# Copyright 2017 Fraunhofer FKIE +# +# IVA is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IVA is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IVA in the COPYING and COPYING.LESSER files. +# If not, see . + +import os +import sys +import config +import server_runner +from collections import namedtuple +from user_authentication.user import User + +ROOT_DIR = os.path.dirname(os.path.realpath(__file__)) +CONFIG_FILES = namedtuple('CONFIG_FILES', ['default', 'test'])('/config.ini', '/dummy/config.ini') +TEST_MODE_OPT = 'test' + + +def main(): + config_file = get_config_file() + config.load_configuration_from_file(config_file) + server_runner.run_iva_server(config_file) + server_runner.run_cve_search_server() + create_dummy_user_if_no_user_exists() + return 0 + + +def get_config_file(): + if is_test_mode(): + return ROOT_DIR + CONFIG_FILES.test + return ROOT_DIR + CONFIG_FILES.default + + +def create_dummy_user_if_no_user_exists(): + user = User() + if user.is_user_collection_empty() or is_test_mode(): + user.create_dummy_user() + + +def is_test_mode(): + if len(sys.argv) > 1: + return sys.argv[1] == TEST_MODE_OPT + return False + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/matching/cpe_matcher.py b/matching/cpe_matcher.py new file mode 100644 index 0000000..819c8de --- /dev/null +++ b/matching/cpe_matcher.py @@ -0,0 +1,85 @@ +import editdistance +from local_repositories.cpe_dict import IVA_CPE_COLLECTION +from matching.search_terms_generator import generate_product_search_terms +from matching.search_terms_generator import remove_version_from_search_terms +from matching.search_terms_generator import generate_vendor_filters +from matching.software_formatter import FormattedSoftware +from database import Database +from matching import cpe_sorter +from wfn.wfn_converter import WFNConverter + + +class CPEMatcher: + def __init__(self): + self.db = Database() + self.wfn_creator = WFNConverter() + + def search_cpes_for_software(self, software): + formatted_sw = FormattedSoftware(software) + product_search_terms = generate_product_search_terms(formatted_sw.product, formatted_sw.vendor) + version = formatted_sw.get_version(product_search_terms) + product_search_terms = remove_version_from_search_terms(product_search_terms, version) + + candidates = self.search_cpe_candidates_by_product(product_search_terms) + filtered_candidates = filter_cpe_candidates_by_vendor(candidates, formatted_sw.vendor, product_search_terms) + ordered_candidates = sort_cpe_candidates_by_version(filtered_candidates, version, product_search_terms) + return ordered_candidates + + def search_cpe_candidates_by_product(self, product_search_terms): + candidates = init_candidates_lists(product_search_terms) + for cpe in self.get_cpe_dictionary(): + wfn_product = cpe.get('wfn').get('product') + for search_term in product_search_terms: + if search_term == 'jdk': + if search_term == wfn_product and (cpe not in candidates.get(search_term)): + candidates.get(search_term).append(cpe) + break + elif search_term == 'jre': + if search_term == wfn_product and (cpe not in candidates.get(search_term)): + candidates.get(search_term).append(cpe) + break + else: + if are_strings_similar(wfn_product, search_term) and (cpe not in candidates.get(search_term)): + candidates.get(search_term).append(cpe) + break + return candidates + + def get_cpe_dictionary(self): + return self.db.get_documents_from_collection(IVA_CPE_COLLECTION) + + +def init_candidates_lists(product_search_terms): + candidate_lists = {} + for search_term in product_search_terms: + candidate_lists[search_term] = [] + return candidate_lists + + +def filter_cpe_candidates_by_vendor(candidates, vendor, product_search_terms): + for search_term in product_search_terms: + candidates[search_term] = filter_by_vendor(candidates[search_term], vendor) + return candidates + + +def filter_by_vendor(cpes, vendor): + filtered_cpe_matches = [] + for cpe in cpes: + for filter_ in generate_vendor_filters(vendor): + wfn_vendor = dict(cpe).get('wfn').get('vendor') + if are_strings_similar(filter_, wfn_vendor): + filtered_cpe_matches.append(cpe) + return filtered_cpe_matches + + +def sort_cpe_candidates_by_version(candidates, version, product_search_terms): + all_sorted_candidates = [] + for search_term in product_search_terms: + if search_term == 'jdk' or search_term == 'jre': + version = '1.' + version + sorted_candidates = cpe_sorter.sort_cpes_by_version(candidates.get(search_term), version) + all_sorted_candidates.extend(sorted_candidates) + return all_sorted_candidates + + +def are_strings_similar(string_a, string_b): + return editdistance.eval(string_a, string_b) <= 2 diff --git a/matching/cpe_matcher_b.py b/matching/cpe_matcher_b.py new file mode 100644 index 0000000..49bb20e --- /dev/null +++ b/matching/cpe_matcher_b.py @@ -0,0 +1,76 @@ +import editdistance +from local_repositories.cpe_dict import IVA_CPE_COLLECTION +from matching.search_terms_generator import generate_product_search_terms +from matching.search_terms_generator import remove_version_from_search_terms +from matching.search_terms_generator import generate_vendor_filters +from matching.software_formatter import FormattedSoftware +from database import Database +from matching import cpe_sorter +from wfn.wfn_converter import WFNConverter + + +class CPEMatcher: + + def __init__(self): + self.db = Database() + self.wfn_creator = WFNConverter() + + def search_cpes_for_software(self, software): + formatted_sw = FormattedSoftware(software) + product_search_terms = generate_product_search_terms(formatted_sw.product, formatted_sw.vendor) + version = formatted_sw.get_version(product_search_terms) + product_search_terms = remove_version_from_search_terms(product_search_terms, version) + + candidates = self.search_cpe_candidates_by_product(product_search_terms) + filtered_candidates = filter_cpe_candidates_by_vendor(candidates, formatted_sw.vendor, product_search_terms) + ordered_candidates = sort_cpe_candidates_by_version(filtered_candidates, version, product_search_terms) + return ordered_candidates + + def search_cpe_candidates_by_product(self, product_search_terms): + candidates = get_cpe_candidates_dict_ordered_by_search_terms(product_search_terms) + for cpe in self.get_cpe_dictionary(): + wfn_product = cpe.get('wfn').get('product') + for search_term in product_search_terms: + if are_strings_similar(wfn_product, search_term) and (cpe not in candidates.get(search_term)): + print(wfn_product + "-" + search_term) + candidates.get(search_term).append(cpe) + break + return candidates + + def get_cpe_dictionary(self): + return self.db.get_documents_from_collection(IVA_CPE_COLLECTION) + + +def get_cpe_candidates_dict_ordered_by_search_terms(product_search_terms): + st_cpes = {} + for search_term in product_search_terms: + st_cpes[search_term] = [] + return st_cpes + + +def filter_cpe_candidates_by_vendor(candidates, vendor, product_search_terms): + for search_term in product_search_terms: + candidates[search_term] = filter_by_vendor(candidates[search_term], vendor) + return candidates + + +def filter_by_vendor(cpes, vendor): + filtered_cpe_matches = [] + for cpe in cpes: + for filter_ in generate_vendor_filters(vendor): + wfn_vendor = dict(cpe).get('wfn').get('vendor') + if are_strings_similar(filter_, wfn_vendor): + filtered_cpe_matches.append(cpe) + return filtered_cpe_matches + + +def sort_cpe_candidates_by_version(candidates, version, product_search_terms): + all_sorted_candidates = [] + for search_term in product_search_terms: + sorted_candidates = cpe_sorter.sort_cpes_by_version(candidates.get(search_term), version) + all_sorted_candidates.extend(sorted_candidates) + return all_sorted_candidates + + +def are_strings_similar(string_a, string_b): + return not editdistance.eval(string_a, string_b) > 2 \ No newline at end of file diff --git a/matching/cpe_matcher_jaro_winkler.py b/matching/cpe_matcher_jaro_winkler.py new file mode 100644 index 0000000..67767b0 --- /dev/null +++ b/matching/cpe_matcher_jaro_winkler.py @@ -0,0 +1,54 @@ +import editdistance +from local_repositories.cpe_dict import IVA_CPE_COLLECTION +from matching.search_terms_generator import generate_product_search_terms +from matching.search_terms_generator import remove_version_from_search_terms +from matching.search_terms_generator import generate_vendor_filters +from matching.software_formatter import FormattedSoftware +from database import Database +from matching import cpe_sorter +import jellyfish +from wfn.wfn_converter import WFNConverter + + +class CPEMatcher: + + def __init__(self): + self.db = Database() + self.wfn_creator = WFNConverter() + + def search_cpes_for_software(self, software): + formatted_sw = FormattedSoftware(software) + product_search_terms = generate_product_search_terms(formatted_sw.product, formatted_sw.vendor) + version = formatted_sw.get_version(product_search_terms) + # product_search_terms = remove_version_from_search_terms(product_search_terms, version) + + candidates = self.search_cpe_candidates_by_product(formatted_sw.product) + filtered_candidates = filter_cpe_candidates_by_vendor(candidates, formatted_sw.vendor) + sorted_candidates = cpe_sorter.sort_cpes_by_version(filtered_candidates, version) + return sorted_candidates + + def search_cpe_candidates_by_product(self, product): + candidates = [] + for cpe in self.get_cpe_dictionary(): + wfn_product = cpe.get('wfn').get('product') + if are_strings_similar(product, wfn_product) and (cpe not in candidates): + candidates.append(cpe) + return candidates + + def get_cpe_dictionary(self): + return self.db.get_documents_from_collection(IVA_CPE_COLLECTION) + + +def filter_cpe_candidates_by_vendor(candidates, vendor): + filtered_cpe_matches = [] + for candidate in candidates: + wfn_vendor = candidate.get('wfn').get('vendor') + if are_strings_similar(vendor, wfn_vendor): + filtered_cpe_matches.append(candidate) + return filtered_cpe_matches + + +def are_strings_similar(string_a, string_b): + d = jellyfish.jaro_winkler(string_a, string_b) + # print(string_a + "==" + string_b + ':' + str(d)) + return d >= 0.9 diff --git a/matching/cpe_matcher_jaro_winkler_b.py b/matching/cpe_matcher_jaro_winkler_b.py new file mode 100644 index 0000000..4d870b2 --- /dev/null +++ b/matching/cpe_matcher_jaro_winkler_b.py @@ -0,0 +1,76 @@ +import editdistance +from local_repositories.cpe_dict import IVA_CPE_COLLECTION +from matching.search_terms_generator import generate_product_search_terms +from matching.search_terms_generator import remove_version_from_search_terms +from matching.search_terms_generator import generate_vendor_filters +from matching.software_formatter import FormattedSoftware +from database import Database +from matching import cpe_sorter +from wfn.wfn_converter import WFNConverter +import jellyfish + +class CPEMatcher: + + def __init__(self): + self.db = Database() + self.wfn_creator = WFNConverter() + + def search_cpes_for_software(self, software): + formatted_sw = FormattedSoftware(software) + product_search_terms = generate_product_search_terms(formatted_sw.product, formatted_sw.vendor) + version = formatted_sw.get_version(product_search_terms) + product_search_terms = remove_version_from_search_terms(product_search_terms, version) + + candidates = self.search_cpe_candidates_by_product(product_search_terms) + filtered_candidates = filter_cpe_candidates_by_vendor(candidates, formatted_sw.vendor, product_search_terms) + ordered_candidates = sort_cpe_candidates_by_version(filtered_candidates, version, product_search_terms) + return ordered_candidates + + def search_cpe_candidates_by_product(self, product_search_terms): + candidates = get_cpe_candidates_dict_ordered_by_search_terms(product_search_terms) + for cpe in self.get_cpe_dictionary(): + wfn_product = cpe.get('wfn').get('product') + for search_term in product_search_terms: + if are_strings_similar(wfn_product, search_term) and (cpe not in candidates.get(search_term)): + candidates.get(search_term).append(cpe) + break + return candidates + + def get_cpe_dictionary(self): + return self.db.get_documents_from_collection(IVA_CPE_COLLECTION) + + +def get_cpe_candidates_dict_ordered_by_search_terms(product_search_terms): + st_cpes = {} + for search_term in product_search_terms: + st_cpes[search_term] = [] + return st_cpes + + +def filter_cpe_candidates_by_vendor(candidates, vendor, product_search_terms): + for search_term in product_search_terms: + candidates[search_term] = filter_by_vendor(candidates[search_term], vendor) + return candidates + + +def filter_by_vendor(cpes, vendor): + filtered_cpe_matches = [] + for cpe in cpes: + for filter_ in generate_vendor_filters(vendor): + wfn_vendor = dict(cpe).get('wfn').get('vendor') + if are_strings_similar(filter_, wfn_vendor): + filtered_cpe_matches.append(cpe) + return filtered_cpe_matches + + +def sort_cpe_candidates_by_version(candidates, version, product_search_terms): + all_sorted_candidates = [] + for search_term in product_search_terms: + sorted_candidates = cpe_sorter.sort_cpes_by_version(candidates.get(search_term), version) + all_sorted_candidates.extend(sorted_candidates) + return all_sorted_candidates + + +def are_strings_similar(string_a, string_b): + d = jellyfish.jaro_winkler(string_a, string_b) + return d >= 0.9 \ No newline at end of file diff --git a/matching/cpe_matcher_utils.py b/matching/cpe_matcher_utils.py new file mode 100644 index 0000000..413dab5 --- /dev/null +++ b/matching/cpe_matcher_utils.py @@ -0,0 +1,48 @@ +import re + +REGEX_START = '^' +REGEX_END = '.*' + + +def create_regex(search_text): + return REGEX_START + re.escape(search_text) + REGEX_END + + +def add_matches_to_list(matches, list_): + for m in matches: + if m not in list_: + list_.append(m) + + +# def calculate_symmetric_difference_between_two_cpe_lists(cpe_list_a, cpe_list_b): +# if len(cpe_list_a) > len(cpe_list_b): +# return calculate_symmetric_difference(cpe_list_a, cpe_list_b) +# return calculate_symmetric_difference(cpe_list_b, cpe_list_a) +# +# +# def calculate_symmetric_difference(cpe_list_a, cpe_list_b): +# difference_set = [] +# for a in cpe_list_a: +# is_common_element = False +# for b in cpe_list_b: +# if dict(a).get('uri_binding') == dict(b).get('uri_binding'): +# is_common_element = True +# break +# if not is_common_element: +# difference_set.append(a) +# return difference_set +# +# +# def calculate_intersection_between_two_cpe_lists(cpe_list_a, cpe_list_b): +# if len(cpe_list_a) > len(cpe_list_b): +# return calculate_intersection(cpe_list_a, cpe_list_b) +# return calculate_intersection(cpe_list_b, cpe_list_a) +# +# +# def calculate_intersection(cpe_list_a, cpe_list_b): +# intersection_set = [] +# for a in cpe_list_a: +# for b in cpe_list_b: +# if dict(a).get('uri_binding') == dict(b).get('uri_binding'): +# intersection_set.append(a) +# return intersection_set diff --git a/matching/cpe_set_operations.py b/matching/cpe_set_operations.py new file mode 100644 index 0000000..e1d45bb --- /dev/null +++ b/matching/cpe_set_operations.py @@ -0,0 +1,29 @@ + + +def calculate_symmetric_difference_between_two_cpe_lists(cpe_list_a, cpe_list_b): + asymmetric_difference_a_b = calculate_asymmetric_difference(cpe_list_a, cpe_list_b) + asymmetric_difference_b_a = calculate_asymmetric_difference(cpe_list_b, cpe_list_a) + asymmetric_difference_a_b.extend(asymmetric_difference_b_a) + return asymmetric_difference_a_b + + +def calculate_asymmetric_difference(cpe_list_a, cpe_list_b): + difference_set = [] + for a in cpe_list_a: + if a not in cpe_list_b: + difference_set.append(a) + return difference_set + + +def calculate_intersection_between_two_cpe_lists(cpe_list_a, cpe_list_b): + if len(cpe_list_a) > len(cpe_list_b): + return calculate_intersection(cpe_list_a, cpe_list_b) + return calculate_intersection(cpe_list_b, cpe_list_a) + + +def calculate_intersection(cpe_list_a, cpe_list_b): + intersection_set = [] + for a in cpe_list_a: + if a in cpe_list_b: + intersection_set.append(a) + return intersection_set diff --git a/matching/cpe_sorter.py b/matching/cpe_sorter.py new file mode 100644 index 0000000..7353985 --- /dev/null +++ b/matching/cpe_sorter.py @@ -0,0 +1,90 @@ +from matching.cpe_set_operations import calculate_symmetric_difference_between_two_cpe_lists +from wfn.wfn_converter import WFNConverter + + +def sort_cpes_by_version(cpes, software_version): + version_prefixes = create_version_prefixes(software_version) + if len(version_prefixes) > 0: + return sort_cpes_by_version_prefixes(cpes, version_prefixes) + if is_year(software_version): + return sort_cpes_by_year_version(cpes, software_version) + return cpes + + +def sort_cpes_by_version_prefixes(cpes, version_prefixes): + # Example: + # version = 4.7.2-3 + # version_prefixes = [4, 4.7, 4.7.2-3] + sorted_cpes = [] + for prefix in version_prefixes: + for cpe in cpes: + cpe_version = get_cpe_version(cpe) + if has_prefix(prefix, cpe_version) and not_in_sorted_list(cpe, sorted_cpes): + sorted_cpes.append(cpe) + unsorted_cpes = calculate_symmetric_difference_between_two_cpe_lists(sorted_cpes, cpes) + sorted_cpes.extend(unsorted_cpes) + return sorted_cpes + + +def sort_cpes_by_year_version(cpes, software_version): + sorted_cpes = [] + cpes_year_not_equal = [] + for cpe in cpes: + cpe_version = get_cpe_version(cpe) + if cpe_version == software_version: + sorted_cpes.append(cpe) + elif str(cpe_version).isdigit(): + cpes_year_not_equal.append(cpe) + sorted_cpes.extend(cpes_year_not_equal) + unsorted_cpes = calculate_symmetric_difference_between_two_cpe_lists(sorted_cpes, cpes) + sorted_cpes.extend(unsorted_cpes) + return sorted_cpes + + +def sort_cpes_by_operating_system(cpes, os): + sorted_cpes = [] + for cpe in cpes: + cpe_target_software = get_cpe_target_sw(cpe) + if is_same_os(cpe_target_software, os) and not_in_sorted_list(cpe, sorted_cpes): + if len(cpe_target_software) == len(os): + sorted_cpes.insert(0, cpe) + else: + sorted_cpes.append(cpe) + unsorted_cpes = calculate_symmetric_difference_between_two_cpe_lists(sorted_cpes, cpes) + sorted_cpes.extend(unsorted_cpes) + return sorted_cpes + + +def create_version_prefixes(software_version): + version_elements = str(software_version).split('.') + if len(version_elements) > 1: + prefixes = [version_elements[0]] + for i in range(1, len(version_elements)): + prefixes.append(prefixes[i - 1] + '.' + str(version_elements[i])) + prefixes = list(reversed(prefixes)) + return prefixes + return [] + + +def has_prefix(version_prefix, uri_binding_version): + return version_prefix in uri_binding_version and version_prefix[0] == uri_binding_version[0] + + +def is_same_os(uri_binding_os, os): + return (uri_binding_os in os) or (os in uri_binding_os) + + +def not_in_sorted_list(uri_binding, sorted_uri_bindings): + return not sorted_uri_bindings.__contains__(uri_binding) + + +def is_year(software_version): + return len(software_version) == 4 and str(software_version).isdigit() + + +def get_cpe_version(cpe): + return cpe.get('wfn').get('version') + + +def get_cpe_target_sw(cpe): + return cpe.get('wfn').get('target_sw') \ No newline at end of file diff --git a/matching/cve_matcher.py b/matching/cve_matcher.py new file mode 100644 index 0000000..eeb522d --- /dev/null +++ b/matching/cve_matcher.py @@ -0,0 +1,163 @@ +import editdistance +from database import Database +from inventory.inventory import Inventory +from wfn.wfn_converter import WFNConverter +from local_repositories.cve_feeds import IVA_CVE_COLLECTION + + +class CVEMatcher: + + def __init__(self): + self.db = Database() + self.wfn_converter = WFNConverter() + self.inventory = Inventory() + + def search_cves_for_cpe(self, uri_binding): + wfn = self.wfn_converter.convert_cpe_uri_to_wfn(uri_binding) + matches_a = self.search_cves(wfn) + matches_b = self.search_cves_with_summary(wfn) + merge_lists(matches_a, matches_b) + return matches_a + + def search_cves(self, wfn): + cve_matches = [] + for vendor in self.get_vendor_candidates(wfn): + for product in self.get_product_candidates(wfn): + tmp_cve_matches = self.search_cves_by_product_and_vendor(product, vendor) + filtered_matches = filter_cpe_entries_in_cve_matches_by_version(tmp_cve_matches, wfn) + cve_matches.extend(filtered_matches) + return cve_matches + + def get_vendor_candidates(self, wfn): + candidates = [] + wfn_vendor = get_vendor_from_wfn(wfn) + for v in self.inventory.get_vendors(): + if are_strings_similar(v, wfn_vendor): + candidates.append(v) + return candidates + + def get_product_candidates(self, wfn): + candidates = [] + wfn_product = get_product_from_wfn(wfn) + for p in self.inventory.get_products(): + if are_strings_similar(p, wfn_product): + candidates.append(p) + wfn_vendor = get_vendor_from_wfn(wfn) + if wfn_vendor in wfn_product: + candidates.append(remove_vendor_from_product(wfn_product, wfn_vendor)) + else: + candidates.append(add_vendor_to_product(wfn_vendor, wfn_product, 'left')) + candidates.append(add_vendor_to_product(wfn_vendor, wfn_product, 'right')) + return candidates + + def search_cves_by_product_and_vendor(self, product, vendor): + search_condition = create_search_condition(product, vendor) + aggregation = create_aggregation(product, vendor) + return self.db.search_documents_and_aggregate(search_condition, aggregation, IVA_CVE_COLLECTION) + + def search_cves_with_summary(self, wfn): + matches = [] + for cve in self.get_cve_without_cpe_entries(): + if is_product_and_vendor_in_cve_summary(cve, wfn): + matches.append(cve) + return matches + + def get_cve_without_cpe_entries(self): + return self.db.search_documents_in_collection({'cpe_entries': {'$size': 0}}, IVA_CVE_COLLECTION) + + +def is_product_and_vendor_in_cve_summary(cve, wfn): + summary_words = get_summary_words(cve) + return is_word_in_summary(get_vendor_from_wfn(wfn), summary_words) and \ + is_word_in_summary(get_product_from_wfn(wfn), summary_words) + + +def is_word_in_summary(word, summary_words): + for s_word in summary_words: + if are_strings_similar(word, s_word): + return True + return False + + +def filter_cpe_entries_in_cve_matches_by_version(cve_matches, wfn): + filtered_matches = [] + for cve_match in cve_matches: + updated_cpe_entries = [] + for cve_cpe in get_cpes_from_cve(cve_match): + if is_main_version_equal(cve_cpe, wfn): + updated_cpe_entries.append(cve_cpe) + if len(updated_cpe_entries) > 0: + cve_match.update({'cpe_entries': updated_cpe_entries}) + filtered_matches.append(cve_match) + return filtered_matches + + +def get_cpes_from_cve(cve): + return cve.get('cpe_entries') + + +def create_search_condition(product, vendor): + return {'cpe_entries': {'$elemMatch': {'$and': [{'wfn.product': product}, {'wfn.vendor': vendor}]}}} + + +def create_aggregation(product, vendor): + return {'cpe_entries': {'$filter': create_aggregation_filter(product, vendor)}, '_id': 0, 'cve_id': 1, 'cve_summary': 1} + + +def create_aggregation_filter(product, vendor): + return {'input': '$cpe_entries', 'as': 'cpe_entries', + 'cond': {'$and': [{'$eq': ['$$cpe_entries.wfn.product', product]}, + {'$eq': ['$$cpe_entries.wfn.vendor', vendor]}]}} + + +def is_same_version(cve_cpe, wfn): + return cve_cpe.get('wfn').get('version') == wfn.get('version') + + +def is_main_version_equal(cve_cpe, wfn): + return is_version_any(wfn) or get_main_version(cve_cpe.get('wfn')) == get_main_version(wfn) + + +def is_version_any(wfn): + main_ver = get_main_version(wfn) + return main_ver == '*' or main_ver == 'ANY' + + +def get_main_version(wfn): + return wfn.get('version').split('.')[0] + + +def merge_lists(list_a, list_b): + for e in list_b: + if e not in list_a: + list_a.append(e) + + +def are_strings_similar(string_a, string_b): + return editdistance.eval(string_a, string_b) <= 2 + + +def get_vendor_from_wfn(wfn): + return wfn.get('vendor').lower() + + +def get_product_from_wfn(wfn): + return wfn.get('product').lower() + + +def get_summary_words(cve): + summary = str(cve.get('cve_summary')).lower() + summary_words = summary.split() + return summary_words + + +def remove_vendor_from_product(product, vendor): + product_without_vendor = product.replace(vendor, '') + product_without_vendor = product_without_vendor.replace('_', '') + return product_without_vendor + + +def add_vendor_to_product(vendor, product, position): + if position == 'left': + return vendor + '_' + product + return product + '_' + vendor diff --git a/matching/search_terms_generator.py b/matching/search_terms_generator.py new file mode 100644 index 0000000..ae49ccf --- /dev/null +++ b/matching/search_terms_generator.py @@ -0,0 +1,85 @@ +from copy import copy + + +def generate_product_search_terms(product, vendor): + if product != '': + search_terms = split(product) + search_terms_no_vendor = remove_vendor_from_product_search_terms(copy(search_terms), vendor) + composed_search_terms_no_vendor = generate_composed_search_terms(search_terms_no_vendor) + composed_search_terms = generate_composed_search_terms(search_terms) + composed_search_terms = list(set(composed_search_terms_no_vendor + composed_search_terms)) + composed_search_terms = order_search_terms_by_length(composed_search_terms) + search_terms = remove_terms_with_less_than_three_chars(search_terms) + search_terms = order_search_terms_by_length(search_terms) + search_terms = composed_search_terms + search_terms + search_terms = add_java_search_terms(search_terms, product) + return search_terms + return [] + + +def add_java_search_terms(search_terms, product): + if 'java' in product: + search_terms.insert(0, 'jre') + if 'development_kit' in product: + search_terms.insert(0, 'jdk') + return search_terms + return search_terms + + +def generate_vendor_filters(vendor): + if vendor != '': + search_terms = split(vendor) + composed_search_terms = generate_composed_search_terms(search_terms) + search_terms = order_search_terms_by_length(search_terms) + return composed_search_terms + search_terms + return [] + + +def split(string): + return str(string).split('_') + + +def remove_vendor_from_product_search_terms(search_terms, vendor): + vendor_elements = split(vendor) + for vendor_element in vendor_elements: + if vendor_element in search_terms: + search_terms.remove(vendor_element) + return search_terms + + +def generate_composed_search_terms(search_terms): + limit = get_composed_search_terms_limit(search_terms) + if limit > 0: + composed_search_terms = [] + composed_search_term = search_terms[0] + for i in range(limit): + composed_search_term += '_' + search_terms[i + 1] + composed_search_terms.insert(0, composed_search_term) + return composed_search_terms + return [] + + +def remove_terms_with_less_than_three_chars(search_terms): + cleaned_search_terms = [] + for search_phrase in search_terms: + if len(search_phrase) > 2: + cleaned_search_terms.append(search_phrase) + return cleaned_search_terms + + +def order_search_terms_by_length(search_terms): + search_terms.sort(key=lambda s: len(s)) + search_terms.reverse() + return search_terms + + +def get_composed_search_terms_limit(search_terms): + st_len = len(search_terms) + if st_len > 0: + return st_len - 1 + + +def remove_version_from_search_terms(search_terms, version): + if version in search_terms: + search_terms.remove(version) + return search_terms diff --git a/matching/software_formatter.py b/matching/software_formatter.py new file mode 100644 index 0000000..c4d490c --- /dev/null +++ b/matching/software_formatter.py @@ -0,0 +1,66 @@ +class FormattedSoftware: + def __init__(self, software): + self.software = software + + @property + def product(self): + return format_product(self.software) + + @property + def vendor(self): + return format_vendor(self.software) + + def get_version(self, product_search_phrases): + version = self.software.get('version') + if version == '': + return get_version_from_product_search_phrases(product_search_phrases) + return version + + @property + def os(self): + return self.software.get('os') + + +def get_version_from_product_search_phrases(product_search_phrases): + for search_phrase in product_search_phrases: + if search_phrase.isdigit() and len(search_phrase) == 4: + return search_phrase + return '' + + +def format_product(software): + return format_string(str(software.get('product'))) + + +def format_vendor(software): + return format_string(str(software.get('vendor'))) + + +def format_string(string): + formatted_str = replace_spaces_by_underscore(string) + formatted_str = formatted_str.lower() + formatted_str = replace_parenthesis_by_underscores(formatted_str) + formatted_str = replace_double_underscore_by_one_underscore(formatted_str) + formatted_str = remove_last_underscore(formatted_str) + return formatted_str + + +def replace_spaces_by_underscore(string): + return string.replace(' ', '_') + + +def replace_parenthesis_by_underscores(string): + string = string.replace('(', '_') + string = string.replace(')', '_') + return string + + +def replace_double_underscore_by_one_underscore(string): + return string.replace('__', '_') + + +def remove_last_underscore(string): + if string[-1] == '_': + return string[:-1] + return string + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..66c4851 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,8 @@ +django==1.9.7 +editdistance==0.3.1 +pycountry==1.20 +pymongo==3.2.2 +ldap3==2.1.1 +configparser==3.5.0 +pymysql==0.7.9 +gnupg==2.2.0 \ No newline at end of file diff --git a/server_runner.py b/server_runner.py new file mode 100644 index 0000000..2452efa --- /dev/null +++ b/server_runner.py @@ -0,0 +1,52 @@ +import os +import threading +import config +import logger + +ROOT_DIR = os.path.dirname(os.path.realpath(__file__)) +MANAGE_FILE = ROOT_DIR + '/frontend/manage.py' +INDEX_FILE = 'web/index.py' + + +def run_iva_server(config_file): + logger.info("starting HTTP server for IVA") + IvaHttpServerThread(config_file).start() + + +def run_cve_search_server(): + logger.info("starting HTTP server for cve-search") + if is_cve_search_installed(): + CveSearchHttpServerThread().start() + else: + logger.error("cve-search was not found in " + config.get_cve_search_dir()) + + +def is_cve_search_installed(): + return os.path.exists(get_cve_search_index_file()) + + +class IvaHttpServerThread(threading.Thread): + + def __init__(self, config_file): + threading.Thread.__init__(self) + self.config_file = config_file + + def run(self): + os.system('python3 ' + MANAGE_FILE + ' runserver ' + get_iva_server_address() + ' ' + self.config_file) + + +def get_iva_server_address(): + return config.get_frontend_host() + ':' + config.get_frontend_port() + + +class CveSearchHttpServerThread(threading.Thread): + + def __init__(self): + threading.Thread.__init__(self) + + def run(self): + os.system('python3 ' + get_cve_search_index_file()) + + +def get_cve_search_index_file(): + return os.path.join(config.get_cve_search_dir(), INDEX_FILE) \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/dict_tester.py b/tests/dict_tester.py new file mode 100644 index 0000000..c8b93ee --- /dev/null +++ b/tests/dict_tester.py @@ -0,0 +1,16 @@ +import unittest + + +class DictTester(unittest.TestCase): + + def assertEqualKeys(self, dict1, dict2): + keys_dict1 = self.getSortedKeys(dict1) + keys_dict2 = self.getSortedKeys(dict2) + self.assertEqual(keys_dict1, keys_dict2) + + def assertEqualValues(self, dict1, dict2): + for key, value_dict1 in dict1.items(): + self.assertEqual(value_dict1, dict2[key], "key: " + key + " values not equal " + str(value_dict1) + " != " + str(dict2[key])) + + def getSortedKeys(self, report): + return sorted(report.keys()) \ No newline at end of file diff --git a/tests/mock_config.py b/tests/mock_config.py new file mode 100644 index 0000000..3c6f0b6 --- /dev/null +++ b/tests/mock_config.py @@ -0,0 +1,38 @@ +from unittest.mock import patch +import importlib + +DB_HOST = 'localhost' +DB_PORT = 27017 +IVA_DB_NAME = 'test_iva_db' +CVE_SEARCH_DB_NAME = 'test_cve_search_db' + + +def path_db_host(): + return patch('config.get_database_host', return_value=DB_HOST) + + +def path_db_port(): + return patch('config.get_database_port', return_value=DB_PORT) + + +def path_db_name(): + return patch('config.get_database_name', return_value=IVA_DB_NAME) + + +def path_cve_search_db_name(): + return patch('config.get_cve_search_db_name', return_value=CVE_SEARCH_DB_NAME) + + +def path_db_auth_enabled(): + return patch('config.is_database_authentication_enabled', return_value=False) + + +def patch_config_for(module_name=None, class_name=None): + with path_db_host(): + with path_db_port(): + with path_db_name(): + with path_db_auth_enabled(): + with path_cve_search_db_name(): + module = importlib.import_module(module_name) + class_ = getattr(module, class_name) + return class_() diff --git a/tests/resources/config.ini b/tests/resources/config.ini new file mode 100644 index 0000000..70fc032 --- /dev/null +++ b/tests/resources/config.ini @@ -0,0 +1,52 @@ +[test-section] +test-option=test option + +[database] +host=localhost +port=27017 +name=iva +authentication=1 +user=user +password=password + +[inventory-database] +host=localhost +user=root +password=123 +name=glpi + +[frontend] +host=192.168.56.125 +port=8080 + +[cve-search] +dir=/home/luis/iva_deb/iva/cve_search +db=cvedb +url=http://192.168.56.125:5000 + +[smtp] +host=172.17.0.2 +port=25 +user=test +password=123 +sender=iva +receiver=test@localdomain +smtps=1 +starttls=1 +verify_server_cert=1 +ca_cert_file=/usr/local/share/iva/ssl/smtp/ca_cert.pem + +[gpg] +required=1 +home_dir=/usr/local/share/iva/gpg +pub_key_file=/usr/local/share/iva/gpg/0xF4G24G5Q.asc + +[ldap] +host=192.168.56.2 +port=389 +base_dn=ou=users,dc=honeynet,dc=de +tls=0 +cacert=/home/luis/ldap_slapd_cacert.pem + +[logging] +file=iva.log \ No newline at end of file diff --git a/tests/resources/gpg/iva_pri.asc b/tests/resources/gpg/iva_pri.asc new file mode 100644 index 0000000..92d1c93 --- /dev/null +++ b/tests/resources/gpg/iva_pri.asc @@ -0,0 +1,57 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: GnuPG v1 + +lQOYBFjSicgBCAC2szKCLGNKzb4hZIQjb1L2m/wiUmLYgRPQ4aU1hzCK+VijXWTj +peocSBO+sp5HVBO+I+s3rrGdsCykkMv+axM/BHfQPytmMWptmpBnmQWigWKzdiuN +0p84fmFPgl9H33RCADWDWMkaUtbBNKF0d6Xz1RdL0UjtxUvpSeQHbppu9km6WiRP +yzXy2U+NgsECO23cXyKY5m/F97qO89/CJC7lsbn2ptAhtXkrpjho80Vt81oY2HCB +yONaJtsCOo18OQ9bBWu9MK39Itrwl65q1Z0CYAs5U4818SoQZTQLH6G+fWYFwYev +PVAh1eCNAG1YRH7gXm3u6iEiOocICh6hmCvzABEBAAEAB/4pKTQu6asxFitIwKmY +hiMt1jS20tLcLjB1tfCPn7aLCOQ0Yf0x4JZe6sowA3PvY/zSsKr0r5Hqkd9s8AZV +enAqmWoY67JuYqG0scbo4CvJ0daX0f68BADFLIvl6txmh+xcVkm4Hs+n85PYkW/y +mmY0CgUmmq8QiouwsmoCVqPKbab1JSf2ZBOFM4H2gVOnLmjv616FK5p6TtyN/9fY +lTxyrdb1iriLnN49kFGOHTNegviXXRy7jVhgq7YUnwRdhcxI4A+fX1ziiOKRXO7v +CQPbpCTPsPN6rB64mz7AX65VvO3UJVuwo0v6UmYyWVp9DZvE+FGn2pRwDJpn9QUd +G8IBBADUDDpSdvAW40IPVxPpJ1KbNsuebqT7poaT5uA2fygmsCdTLhPtVzFjy0zt +OSsE6obX80thouuPju/oZTJzZGyJHzFNUZcysMgQ9i5IzdKg+SKKhVsYvSxnLRm5 +zHilFdprtVHLFGQuubl5S2LP4GFLP6PXrnxmXKC9xS+SogTtAQQA3JG0cRcDWQSd +fWHGEcYIdpMfexIILIjV7G/b8EWeVdn9SpW1cIsRZKgr87UZxLAvec0YnMXlZA6O +GCUit2lNrkGUM4N1ERXql0+1G67wuqjJiU5AORnHbK/EGAel0CBwkMZQ+z+LfwpF +ACrJ+CP8XNS5kgkUqF1y5+NhspvHNPMEALEXiPiWpEe1yUGf/mws2lNq6/xVCFS7 +f3lh7upM/n9TSYmnNhcdsazyEolmSdx5LgnCsBhvjuZniajte6n9/wrJN6D9D6fz +brOwERDc2p6EhUZ6XK+lMsc3pGN6nRThAsjNmAa201PzYFu3skmZLdMYIlYSmknE +yBYx+kQJKcKdRSu0E3Rlc3QgaXZhIDx0ZXN0QGl2YT6JATgEEwECACIFAljSicgC +GwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEErZfpwmt5VPtP0H/j/U/4C6 +lCwQqZw1LtQwFd+yE+1n/gu369jrKc9ljqQXBD0LAiYvETTDCLQlgEEct6boVUlj +Wn+3+TuIlMeFOC0nWcmveBf/NXD5ExkKpt4vBfBQFb3eg/wRgwIuuP2nYN3w4k9/ +rFkgNtsYy8aEtzGUovFaWM+q9NEI762COJxwmc/tLTAf+vEdzgZVwS6QhGTBC3rV +SCkVbq9RAK7fh+tc5rE2jvGwf8/00dFimIq0pEfQGSjJzUb+7HCP1O6oYeY9PAfo +W05c1hEMz59ySDUuLDOIyshHWiQV+uZ32qto6hsNi/JzCVzTMWzAwjz7D9CH2tPy +QdORlRqs7GOnloWdA5gEWNKJyAEIAJoQ2XHzTaPA10p+OkT83uMLEaPa3soDxOrU +8wwrHx/IdrXvg+QvkwqiPXLPwVV6AM0PtP5ucJiHWLYRpVrReIddncUUOv0ltAou +ufkm2aWcUEWTbdysBErcgiwO6QdEyyYL+KFydPBEbF/hXxZxgtJa5ZyrfSnh6Vh4 +hCqKs1Riz/fKyo3DR/sFz1psUELG4VW1DmAvBCvOHOf8Gqv9GI7+IXGdRGmuIdDt +BSloEWZl39mfmKMX2CPAZ8pEhkwdP3F5Z2oaewwWEW0mVvhjFJqEjAUB2n/erVn/ +Ak6/u4b6LAZBIHCvTQuUE5ATvratHBX9JziRi6uQ+4UN6d+UIqEAEQEAAQAH/jel +J0xzYpLnBztmHPim1n/tkwj59+bB9sEUedZmyr6JgmrS3/GuRy8bixMpO3x6bMij +yCzvSaall+JmvDDxoFVP+Dhixq2BDSZyWwRExm585zdB+Pevf3tkanWsSvkvnSoY +P8xcESVoqTX2KghqpiQA5ZaxD6ttZ4DnQmE1KHEtEdbEmddZbRBTHrooUPjXDaDn +JzCWmz8Niv3vHYLdE8saJsljBixXY0EnzDVdcVG9YD4v0FnaivWvqMrtuZEfl2ZZ +pbVpMz3053gh9E5/pHI+KqZFVS2bdzEnuQ31JXFtGLOafYop6i17X/vKL4DXiA74 +pnbMlHjDIVcXMwoeZ8MEAMVYCUkH3dyFekKYzP7n6y7rmj5QVt/wXQAz9o3gg6L/ +kccDot0oGYjI2okTFPI0tjQbSwiKCAHK0rNjr9dfHtmioaxdpd0Xf9zbeT8bWKUZ +uMMHQVzQW4fzRB1Ny1pmuwU03WAw8zAeQeFiAsJ1NhZuAXnbCZyKI3Zkk83cLLfD +BADH28URgBBcvK4Fxl1DSyS8xTxNBHw1ikU1ISU3ovW+xuDnZjWhwbvh/XTh5jY8 +7amHLqO+3yG0uHlDe/OLBAR5va21L6uvpFNwVqzHvWr2HBWKSoM8CGqrDbjoXuXy +mYnE9dWqV1ml7zlWd+kNe+S13g0bRPrplYhiPX5dILw5ywP+MUY47f9LhFlwIs0K +fScSbv73XYxT0J4f0hsZTMgdzNkW03tdfwZOeDcQZoi30qxucmveq9fArN6Zip15 +i0ATe90Q3uZbTWlZLKAkWtA2yCCGvQ4IczfgaFFAuLssmRt4mYPqAG9sOz3gg8I9 +yOpt94NR4ExMjZqzQsQwK9Jz33c7PIkBHwQYAQIACQUCWNKJyAIbDAAKCRBK2X6c +JreVT8WDCACzQjBlNyIEKcut3e3/Qp95s3RmiCez2qne2/5b+oQgefgQ+X4y33ZQ +kUexXBx++KUI5i+686er6xE+TQfzE2QXgmeVtdCrlbMlQ2SLdIzN7bcodpQMxh+9 +/av8VOVJDfazUACjWUsQWb9XuxHhWbck7jXJ69qr+M67taqTZvN5z+Zbhk5LUFS9 +21GwhACajv4yj4TBPKnRLzk+FYec7hP9G38G4tawPa6BrI11aBN2zloZpHJcgqQq +3XG2t3c5RMKBdOZ5px9Op/oH0tW1BH5qttQVcpsN/1+eFboEU0banAQUNdD7tnWH +SWC2pCupifTPqLpBYK79UONVVujOHo9X +=zkwu +-----END PGP PRIVATE KEY BLOCK----- diff --git a/tests/resources/gpg/iva_pub.asc b/tests/resources/gpg/iva_pub.asc new file mode 100644 index 0000000..a2b9f2e --- /dev/null +++ b/tests/resources/gpg/iva_pub.asc @@ -0,0 +1,30 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1 + +mQENBFjSicgBCAC2szKCLGNKzb4hZIQjb1L2m/wiUmLYgRPQ4aU1hzCK+VijXWTj +peocSBO+sp5HVBO+I+s3rrGdsCykkMv+axM/BHfQPytmMWptmpBnmQWigWKzdiuN +0p84fmFPgl9H33RCADWDWMkaUtbBNKF0d6Xz1RdL0UjtxUvpSeQHbppu9km6WiRP +yzXy2U+NgsECO23cXyKY5m/F97qO89/CJC7lsbn2ptAhtXkrpjho80Vt81oY2HCB +yONaJtsCOo18OQ9bBWu9MK39Itrwl65q1Z0CYAs5U4818SoQZTQLH6G+fWYFwYev +PVAh1eCNAG1YRH7gXm3u6iEiOocICh6hmCvzABEBAAG0E3Rlc3QgaXZhIDx0ZXN0 +QGl2YT6JATgEEwECACIFAljSicgCGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheA +AAoJEErZfpwmt5VPtP0H/j/U/4C6lCwQqZw1LtQwFd+yE+1n/gu369jrKc9ljqQX +BD0LAiYvETTDCLQlgEEct6boVUljWn+3+TuIlMeFOC0nWcmveBf/NXD5ExkKpt4v +BfBQFb3eg/wRgwIuuP2nYN3w4k9/rFkgNtsYy8aEtzGUovFaWM+q9NEI762COJxw +mc/tLTAf+vEdzgZVwS6QhGTBC3rVSCkVbq9RAK7fh+tc5rE2jvGwf8/00dFimIq0 +pEfQGSjJzUb+7HCP1O6oYeY9PAfoW05c1hEMz59ySDUuLDOIyshHWiQV+uZ32qto +6hsNi/JzCVzTMWzAwjz7D9CH2tPyQdORlRqs7GOnloW5AQ0EWNKJyAEIAJoQ2XHz +TaPA10p+OkT83uMLEaPa3soDxOrU8wwrHx/IdrXvg+QvkwqiPXLPwVV6AM0PtP5u +cJiHWLYRpVrReIddncUUOv0ltAouufkm2aWcUEWTbdysBErcgiwO6QdEyyYL+KFy +dPBEbF/hXxZxgtJa5ZyrfSnh6Vh4hCqKs1Riz/fKyo3DR/sFz1psUELG4VW1DmAv +BCvOHOf8Gqv9GI7+IXGdRGmuIdDtBSloEWZl39mfmKMX2CPAZ8pEhkwdP3F5Z2oa +ewwWEW0mVvhjFJqEjAUB2n/erVn/Ak6/u4b6LAZBIHCvTQuUE5ATvratHBX9JziR +i6uQ+4UN6d+UIqEAEQEAAYkBHwQYAQIACQUCWNKJyAIbDAAKCRBK2X6cJreVT8WD +CACzQjBlNyIEKcut3e3/Qp95s3RmiCez2qne2/5b+oQgefgQ+X4y33ZQkUexXBx+ ++KUI5i+686er6xE+TQfzE2QXgmeVtdCrlbMlQ2SLdIzN7bcodpQMxh+9/av8VOVJ +DfazUACjWUsQWb9XuxHhWbck7jXJ69qr+M67taqTZvN5z+Zbhk5LUFS921GwhACa +jv4yj4TBPKnRLzk+FYec7hP9G38G4tawPa6BrI11aBN2zloZpHJcgqQq3XG2t3c5 +RMKBdOZ5px9Op/oH0tW1BH5qttQVcpsN/1+eFboEU0banAQUNdD7tnWHSWC2pCup +ifTPqLpBYK79UONVVujOHo9X +=Iivw +-----END PGP PUBLIC KEY BLOCK----- diff --git a/tests/test_alerts/__init__.py b/tests/test_alerts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_alerts/test_alerts.py b/tests/test_alerts/test_alerts.py new file mode 100644 index 0000000..eb16e4f --- /dev/null +++ b/tests/test_alerts/test_alerts.py @@ -0,0 +1,338 @@ +import unittest +import datetime +from pymongo import MongoClient +from unittest.mock import MagicMock +from alerts.alerts import ALERTS_DB_COLLECTION +from alerts.alert_sender import EmailSender +from inventory.inventory import INVENTORY_DB_COLLECTION +from tests.mock_config import * + +SOFTWARE_ID = 'jdf5fh8qk5flo5a5' +CVE_ID = 'CVE-2012-5252' +DATE = datetime.datetime.now().isoformat() +NEW_ALERT_LOG_ENTRY = {'date': DATE, 'event': 'new alert for' + CVE_ID} +ALERT = {'generated_on': DATE, 'software_id': SOFTWARE_ID, 'cves': [CVE_ID], 'status': 'new', + 'log': [NEW_ALERT_LOG_ENTRY], 'notes': ''} + + +class TestAlerts(unittest.TestCase): + + def setUp(self): + self.mongodb_client = MongoClient(DB_HOST, DB_PORT) + self.db = self.mongodb_client[IVA_DB_NAME] + self.alerts_collection = self.db[ALERTS_DB_COLLECTION] + self.inventory_collection = self.db[INVENTORY_DB_COLLECTION] + self.create_alerts_object() + + def create_alerts_object(self): + self.alerts = patch_config_for(module_name='alerts.alerts', class_name='Alerts') + + def test_insert_new_alert(self): + self.insert_new_alert() + + # verify + inserted_alert = self.alerts_collection.find_one({'software_id': SOFTWARE_ID}) + self.assertIsNotNone(inserted_alert) + self.assertEqual(SOFTWARE_ID, inserted_alert.get('software_id')) + cves = inserted_alert.get('cves') + self.assertEqual(1, len(cves)) + self.assertEqual(CVE_ID, cves[0]) + self.assertEqual('new', inserted_alert.get('status')) + self.assertEqual('', inserted_alert.get('notes')) + self.verifyYYMMDD(inserted_alert.get('generated_on')) + log = inserted_alert.get('log') + self.assertEqual(1, len(log)) + self.assertEqual(NEW_ALERT_LOG_ENTRY.get('event'), log[0].get('event')) + + def insert_new_alert(self): + with patch('alerts.alerts.generate_log_entry_for_new_alert') as alerts_logger_mock: + alerts_logger_mock.return_value = NEW_ALERT_LOG_ENTRY + self.alerts.insert_new_alert_for_inventory_item(SOFTWARE_ID, CVE_ID) + + def test_add_cve_to_alert(self): + # mock insert new alert for inventory item + self.alerts_collection.insert_one(ALERT) + + # insert new CVE + new_cve = 'CVE-2016-1252' + self.alerts.add_new_cve_to_alert(SOFTWARE_ID, new_cve) + + # verify + modified_alert = self.alerts_collection.find_one({'software_id': SOFTWARE_ID}) + cves = modified_alert.get('cves') + self.assertEqual(2, len(cves)) + self.assertTrue(new_cve in cves) + self.assertEqual(2, len(modified_alert.get('log'))) + + def test_add_cve_to_alert_set_change_status_to_new_when_status_was_removed(self): + self.alerts_collection.insert_one({'generated_on': '', 'software_id': SOFTWARE_ID, + 'cves': [], 'status': 'removed', 'log': ['entry1', 'entry2'], + 'notes': ''}) + + # add new CVE to alert + with patch('alerts.alerts.generate_log_entry_for_changed_alert_status') as alerts_logger_mock: + alerts_logger_mock.return_value = 'changed to new' + self.alerts.add_new_cve_to_alert(SOFTWARE_ID, 'cve1') + + # verify + modified_alert = self.alerts_collection.find_one({'software_id': SOFTWARE_ID}) + self.assertEqual('new', modified_alert.get('status')) + + def test_add_cve_to_alert_set_change_status_to_new_when_status_was_closed(self): + self.alerts_collection.insert_one({'generated_on': '', 'software_id': SOFTWARE_ID, + 'cves': [], 'status': 'closed', 'log': ['entry1', 'entry2'], + 'notes': ''}) + + # add new CVE to alert + with patch('alerts.alerts.generate_log_entry_for_changed_alert_status') as alerts_logger_mock: + alerts_logger_mock.return_value = 'changed to new' + self.alerts.add_new_cve_to_alert(SOFTWARE_ID, 'cve1') + + # verify + modified_alert = self.alerts_collection.find_one({'software_id': SOFTWARE_ID}) + self.assertEqual('new', modified_alert.get('status')) + self.assertTrue('changed to new' in modified_alert.get('log')) + + def test_remove_cve_from_alert(self): + # mock insert new alert for inventory item + self.alerts_collection.insert_one({'generated_on': '', 'software_id': SOFTWARE_ID, + 'cves': ['cve1', 'cve2'], 'status': 'new', 'log': [NEW_ALERT_LOG_ENTRY], + 'notes': ''}) + + # remove cve from alert + with patch('alerts.alerts.generate_log_entry_for_removed_cve') as alerts_logger_mock: + alerts_logger_mock.return_value = 'cve removed' + self.alerts.remove_cve_from_alert(SOFTWARE_ID, 'cve1') + + # verify + alert = self.alerts_collection.find_one({'software_id': SOFTWARE_ID}) + self.assertEqual(1, len(alert.get('cves'))) + self.assertEqual('cve2', alert.get('cves')[0]) + self.assertEqual('new', alert.get('status')) + log = alert.get('log') + self.assertEqual(2, len(log)) + self.assertTrue('cve removed' in log) + + def test_remove_cve_from_alert_set_status_removed_when_last_cve_removed(self): + # mock insert new alert for inventory item + self.alerts_collection.insert_one(ALERT) + + # remove cve from alert + with patch('alerts.alerts.generate_log_entry_for_changed_alert_status') as alerts_logger_mock: + alerts_logger_mock.return_value = 'status changed' + self.alerts.remove_cve_from_alert(SOFTWARE_ID, CVE_ID) + + # verify + alert = self.alerts_collection.find_one({'software_id': SOFTWARE_ID}) + self.assertEqual(0, len(alert.get('cves'))) + self.assertEqual('removed', alert.get('status')) + log = alert.get('log') + self.assertEqual(3, len(log)) + self.assertTrue('status changed' in log) + + def test_change_status_new_to_closed(self): + # mock insert new alert for inventory item + self.alerts_collection.insert_one(ALERT) + + # change from new to closed + with patch('alerts.alerts.generate_log_entry_for_changed_alert_status') as alerts_logger_mock: + alerts_logger_mock.return_value = 'status changed from new to closed' + self.alerts.change_alert_status(SOFTWARE_ID, 'closed') + + modified_alert = self.alerts_collection.find_one({'software_id': SOFTWARE_ID}) + # verify status was updated + self.assertEqual('closed', modified_alert.get('status')) + # verify status change was logged + self.assertTrue('status changed from new to closed' in modified_alert.get('log')) + # verify alert CVE were not modified + self.assertEqual(1, len(modified_alert.get('cves'))) + self.assertEqual(CVE_ID, modified_alert.get('cves')[0]) + + def test_change_status_new_to_removed(self): + # mock insert new alert for inventory item + self.alerts_collection.insert_one(ALERT) + + # change from new to removed + with patch('alerts.alerts.generate_log_entry_for_changed_alert_status') as alerts_logger_mock: + alerts_logger_mock.return_value = 'status changed from new to removed' + self.alerts.change_alert_status(SOFTWARE_ID, 'removed') + + modified_alert = self.alerts_collection.find_one({'software_id': SOFTWARE_ID}) + # verify status was updated + self.assertEqual('removed', modified_alert.get('status')) + # verify status change was logged + self.assertTrue('status changed from new to removed' in modified_alert.get('log')) + # verify alert CVEs were removed + self.assertEqual(0, len(modified_alert.get('cves'))) + + def test_change_status_closed_to_new(self): + # mock insert new alert for inventory item + self.alerts_collection.insert_one({'generated_on': '', 'software_id': SOFTWARE_ID, 'cves': [CVE_ID], + 'status': 'closed', 'log': ['entry1'], 'notes': ''}) + + # change from closed to new + with patch('alerts.alerts.generate_log_entry_for_changed_alert_status') as alerts_logger_mock: + alerts_logger_mock.return_value = 'status changed from closed to new' + self.alerts.change_alert_status(SOFTWARE_ID, 'new') + + modified_alert = self.alerts_collection.find_one({'software_id': SOFTWARE_ID}) + # verify status was updated + self.assertEqual('new', modified_alert.get('status')) + # verify status change was logged + self.assertTrue('status changed from closed to new' in modified_alert.get('log')) + # verify alert CVEs not removed + self.assertTrue(CVE_ID in modified_alert.get('cves')) + + def test_change_status_closed_to_new_not_possible_when_alerts_has_no_cve(self): + # mock insert new alert for inventory item + self.alerts_collection.insert_one({'generated_on': '', 'software_id': SOFTWARE_ID, 'cves': [], + 'status': 'closed', 'log': ['entry1'], 'notes': ''}) + + # change from closed to new' + self.alerts.change_alert_status(SOFTWARE_ID, 'new') + + modified_alert = self.alerts_collection.find_one({'software_id': SOFTWARE_ID}) + # verify status was still closed + self.assertEqual('closed', modified_alert.get('status')) + # verify no new log entry was added + self.assertEqual(1, len(modified_alert.get('log'))) + # verify alert CVEs were not modified + self.assertEqual(0, len(modified_alert.get('cves'))) + + def test_change_status_closed_to_removed(self): + # mock insert new alert for inventory item + self.alerts_collection.insert_one({'generated_on': '', 'software_id': SOFTWARE_ID, + 'cves': ['cve1', 'cve2'], 'status': 'closed', 'log': ['entry1'], 'notes': ''}) + + # change from closed to removed' + self.alerts.change_alert_status(SOFTWARE_ID, 'removed') + + modified_alert = self.alerts_collection.find_one({'software_id': SOFTWARE_ID}) + # verify status changed to removed + self.assertEqual('removed', modified_alert.get('status')) + # verify new log entry was added + self.assertEqual(2, len(modified_alert.get('log'))) + # verify alert CVEs were removed + self.assertEqual(0, len(modified_alert.get('cves'))) + + def test_change_status_removed_to_new_is_not_possible(self): + # mock insert new alert for inventory item + self.alerts_collection.insert_one({'generated_on': '', 'software_id': SOFTWARE_ID, + 'cves': [], 'status': 'removed', 'log': ['entry1'], 'notes': ''}) + + # change from removed to new' + self.alerts.change_alert_status(SOFTWARE_ID, 'new') + + modified_alert = self.alerts_collection.find_one({'software_id': SOFTWARE_ID}) + # verify status was still removed + self.assertEqual('removed', modified_alert.get('status')) + # verify no new log entry was added + self.assertEqual(1, len(modified_alert.get('log'))) + + def test_change_status_removed_to_closed(self): + # mock insert new alert for inventory item + self.alerts_collection.insert_one({'generated_on': '', 'software_id': SOFTWARE_ID, + 'cves': [], 'status': 'removed', 'log': ['entry1'], 'notes': ''}) + + # change from removed to closed' + self.alerts.change_alert_status(SOFTWARE_ID, 'closed') + + modified_alert = self.alerts_collection.find_one({'software_id': SOFTWARE_ID}) + # verify status changed to closed + self.assertEqual('closed', modified_alert.get('status')) + # verify new log entry was added + self.assertEqual(2, len(modified_alert.get('log'))) + + def test_update_alert_notes(self): + # mock insert new alert for inventory item + self.alerts_collection.insert_one(ALERT) + + # update notes + self.alerts.update_notes(SOFTWARE_ID, 'new notes text') + + # verify + alert = self.alerts_collection.find_one({'software_id': SOFTWARE_ID}) + self.assertEqual('new notes text', alert.get('notes')) + + def test_get_number_of_new_alerts_return_zero(self): + self.assertEqual(0, self.alerts.get_number_of_new_alerts()) + + def test_get_alerts_return_alerts_sorted_by_descending_date_and_status(self): + # mock insert alerts + alert_1_status_new = {'generated_on': '2016-11-02 12:06:32.992849', 'software_id': '1', 'cves': [], + 'status': 'new', 'log': [], 'notes': ''} + alert_2_status_new = {'generated_on': '2016-11-03 12:06:32.992849', 'software_id': '2', 'cves': [], + 'status': 'new', 'log': [], 'notes': ''} + alert_3_status_new = {'generated_on': '2016-11-03 13:06:32.992849', 'software_id': '3', 'cves': [], + 'status': 'new', 'log': [], 'notes': ''} + alert_1_status_closed = {'generated_on': '2016-08-03 13:06:32.992849', 'software_id': '4', 'cves': [], + 'status': 'closed', 'log': [], 'notes': ''} + alert_2_status_closed = {'generated_on': '2016-10-25 13:06:32.992849', 'software_id': '5', 'cves': [], + 'status': 'closed', 'log': [], 'notes': ''} + alert_1_status_removed = {'generated_on': '2015-07-03 13:06:32.992849', 'software_id': '6', 'cves': [], + 'status': 'removed', 'log': [], 'notes': ''} + alert_2_status_removed = {'generated_on': '2016-07-03 13:06:32.992849', 'software_id': '7', 'cves': [], + 'status': 'removed', 'log': [], 'notes': ''} + + self.alerts_collection.insert_many(documents=[alert_3_status_new, alert_2_status_new, alert_1_status_new, + alert_2_status_closed, alert_1_status_closed, + alert_2_status_removed, alert_1_status_removed]) + + # get alerts + alerts = self.alerts.get_alerts() + + # verify + self.assertEqual(7, len(alerts)) + self.assertEqual('1', alerts[0].get('software_id')) + self.assertEqual('2', alerts[1].get('software_id')) + self.assertEqual('3', alerts[2].get('software_id')) + self.assertEqual('4', alerts[3].get('software_id')) + self.assertEqual('5', alerts[4].get('software_id')) + self.assertEqual('6', alerts[5].get('software_id')) + self.assertEqual('7', alerts[6].get('software_id')) + + def test_send_alert_by_email(self): + self.insert_software_to_db() + self.alerts_collection.insert_one(ALERT) + email_sender_mock = MagicMock(spec=EmailSender) + email_sender_mock.send.return_value = True + with patch('alerts.alerts.EmailSender', return_value=email_sender_mock): + self.assertTrue(self.alerts.send_sw_alert_by_email(SOFTWARE_ID)) + email_sender_mock.send.assert_called_with(self.create_alert_for_email()) + alert = self.alerts_collection.find_one({'software_id': SOFTWARE_ID}) + self.assertEqual('sent', alert.get('status')) + + def test_send_alert_by_email_returns_false_when_email_sender_unable_to_send_email(self): + self.insert_software_to_db() + self.alerts_collection.insert_one(ALERT) + email_sender_mock = MagicMock(spec=EmailSender) + email_sender_mock.send.return_value = False + with patch('alerts.alerts.EmailSender', return_value=email_sender_mock): + self.assertFalse(self.alerts.send_sw_alert_by_email(SOFTWARE_ID)) + + def insert_software_to_db(self): + software = {'id': SOFTWARE_ID, 'version': '8', 'product': 'internet_explorer', + 'vendor': 'microsoft', 'cpe': {'uri_binding': 'cpe:/a:microsoft:internet_explorer:8.0.7601.17514:::de', + 'wfn': {}}, + 'cve_matches': [{'cve_id': CVE_ID, 'positive': 1, 'removed': 0, 'cpe_entries': []}]} + self.inventory_collection.insert_one(software) + + def create_alert_for_email(self): + alert = 'Generated on: ' + str(DATE) + '\n\nSoftware ID: '+SOFTWARE_ID+'\n\n' \ + 'Product: internet_explorer\n\nVendor: microsoft\n\nVersion: 8\n\n' \ + 'CPE: cpe:/a:microsoft:internet_explorer:8.0.7601.17514:::de\n\n' \ + 'CVEs: '+str(ALERT.get('cves'))+'\n\nStatus: '+str(ALERT.get('status'))+'\n\n' \ + 'Log:\n' + str(DATE) + ': new alert for' + CVE_ID + '\n\nNotes: '+str(ALERT.get('notes')) + return alert + + def verifyYYMMDD(self, date): + expected_yymmdd = datetime.datetime.utcnow().strftime("%Y-%m-%d") + self.assertRegex(str(date), expected_yymmdd, "Invalid Date") + + def tearDown(self): + self.mongodb_client.drop_database(IVA_DB_NAME) + self.mongodb_client.close() + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_alerts/test_alerts_logger.py b/tests/test_alerts/test_alerts_logger.py new file mode 100644 index 0000000..c804e74 --- /dev/null +++ b/tests/test_alerts/test_alerts_logger.py @@ -0,0 +1,46 @@ +import unittest +import datetime +from alerts import alerts_logger as logger + +CVE = 'CVE-2016-0001' + + +class TestAlertsLogger(unittest.TestCase): + + def setUp(self): + pass + + def test_log_new_alert(self): + expected_log_entry = {'date': datetime.datetime.utcnow(), 'event': 'Alert generated due to ' + CVE} + log_entry = logger.generate_log_entry_for_new_alert(CVE) + self.verify(expected_log_entry, log_entry) + + def test_log_remove_cve(self): + expected_log_entry = {'date': datetime.datetime.utcnow(), 'event': CVE + ' was removed'} + log_entry = logger.generate_log_entry_for_removed_cve(CVE) + self.verify(expected_log_entry, log_entry) + + def test_log_add_new_cve(self): + expected_log_entry = {'date': datetime.datetime.utcnow(), 'event': CVE + ' was added'} + log_entry = logger.generate_log_entry_for_added_cve(CVE) + self.verify(expected_log_entry, log_entry) + + def test_log_change_alert_status(self): + old_alert_status = 'new' + new_alert_status = 'removed' + expected_log_entry = {'date': datetime.datetime.utcnow(), 'event': 'Alert status changed: ' + + old_alert_status + ' to ' + new_alert_status} + log_entry = logger.generate_log_entry_for_changed_alert_status(old_alert_status, new_alert_status) + self.verify(expected_log_entry, log_entry) + + def verify(self, expected_log_entry, log_entry): + self.assertEqual(expected_log_entry.get('event'), log_entry.get('event')) + self.verifyYYMMDD(log_entry.get('date')) + + def verifyYYMMDD(self, date): + expected_yymmdd = datetime.datetime.utcnow().strftime("%Y-%m-%d") + self.assertRegex(str(date), expected_yymmdd, "Invalid Date") + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_alerts/test_gpg_encryption_email_sender.py b/tests/test_alerts/test_gpg_encryption_email_sender.py new file mode 100644 index 0000000..ab36d9a --- /dev/null +++ b/tests/test_alerts/test_gpg_encryption_email_sender.py @@ -0,0 +1,50 @@ +import os +import gnupg +import config +import unittest +from unittest.mock import patch +from alerts.alert_sender import EmailSender + +TEST_FILE_PATH = os.path.dirname(os.path.realpath(__file__)) +TEST_DIR = os.path.abspath(os.path.join(TEST_FILE_PATH, os.pardir)) +GPG_DIR = os.path.join(TEST_DIR, 'resources/gpg/') +PUB_KEY_FILE = os.path.join(GPG_DIR, 'iva_pub.asc') +PRI_KEY_FILE = os.path.join(GPG_DIR, 'iva_pri.asc') +BODY = 'email body that must be encrypted when is required' + +config.reload_configuration(os.path.join(TEST_DIR, 'resources/config.ini')) + + +class TestGPG(unittest.TestCase): + + def setUp(self): + self.gpg = gnupg.GPG(homedir=GPG_DIR) + + def test_email_body_is_encrypted_with_gpg(self): + with patch('config.is_gpg_encryption_enabled', return_value=True): + with patch('config.get_gpg_home_dir', return_value=GPG_DIR): + with patch('config.get_gpg_pub_key_file', return_value=PUB_KEY_FILE): + self.verify_encrypted_body(self.create_email()) + + def test_email_body_is_not_encrypted(self): + with patch('config.is_gpg_encryption_enabled', return_value=False): + email = self.create_email() + self.assertEqual(BODY, email.get_payload()) + + def create_email(self): + return EmailSender().create_email('subject', BODY) + + def verify_encrypted_body(self, email): + self.import_keys() + self.assertEqual(self.get_decrypted_body(email), BODY) + + def import_keys(self): + with open(PRI_KEY_FILE, 'r') as f: + self.gpg.import_keys(f.read()) + + def get_decrypted_body(self, email): + return str(self.gpg.decrypt(email.get_payload())) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_config.py b/tests/test_config.py new file mode 100644 index 0000000..1658954 --- /dev/null +++ b/tests/test_config.py @@ -0,0 +1,241 @@ +from urllib.parse import urljoin +import unittest +import configparser +import os +import config + + +CONFIG_FILE = 'config.ini' +TESTS_DIR = os.path.dirname(os.path.realpath(__file__)) +ROOT_DIR = os.path.abspath(os.path.join(TESTS_DIR, os.pardir)) +PRODUCTION_CONFIG_FILE = os.path.join(ROOT_DIR, CONFIG_FILE) +TEST_CONFIG_FILE = os.path.join(ROOT_DIR, 'tests/resources/' + CONFIG_FILE) +DUMMY_CONFIG_FILE = os.path.join(ROOT_DIR, 'dummy/' + CONFIG_FILE) + + +class TestConfig(unittest.TestCase): + + def setUp(self): + self.expected_config = self.load_configuration(TEST_CONFIG_FILE) + config.load_configuration_from_file(TEST_CONFIG_FILE) + + def test_config_files_exist(self): + self.assertTrue(os.path.exists(PRODUCTION_CONFIG_FILE)) + self.assertTrue(os.path.exists(TEST_CONFIG_FILE)) + self.assertTrue(os.path.exists(DUMMY_CONFIG_FILE)) + + def test_configuration_options_exist(self): + production_config = self.load_configuration(PRODUCTION_CONFIG_FILE) + dummy_config = self.load_configuration(DUMMY_CONFIG_FILE) + test_config = self.load_configuration(TEST_CONFIG_FILE) + + self.verify_config_options_exist(production_config, PRODUCTION_CONFIG_FILE) + self.verify_config_options_exist(dummy_config, DUMMY_CONFIG_FILE) + self.verify_config_options_exist(test_config, TEST_CONFIG_FILE) + + def verify_config_options_exist(self, config_, config_file): + self.assertIsNotNone(config_.get('database', 'host'), config_file) + self.assertIsNotNone(config_.get('database', 'port'), config_file) + self.assertIsNotNone(config_.get('database', 'name'), config_file) + self.assertIsNotNone(config_.get('database', 'user'), config_file) + self.assertIsNotNone(config_.get('database', 'password'), config_file) + self.assertIsNotNone(config_.get('database', 'authentication'), config_file) + self.assertIsNotNone(config_.get('inventory-database', 'host'), config_file) + self.assertIsNotNone(config_.get('inventory-database', 'user'), config_file) + self.assertIsNotNone(config_.get('inventory-database', 'password'), config_file) + self.assertIsNotNone(config_.get('inventory-database', 'name'), config_file) + self.assertIsNotNone(config_.get('frontend', 'host'), config_file) + self.assertIsNotNone(config_.get('frontend', 'port'), config_file) + self.assertIsNotNone(config_.get('cve-search', 'dir'), config_file) + self.assertIsNotNone(config_.get('cve-search', 'db'), config_file) + self.assertIsNotNone(config_.get('cve-search', 'url'), config_file) + self.assertIsNotNone(config_.get('smtp', 'host'), config_file) + self.assertIsNotNone(config_.get('smtp', 'port'), config_file) + self.assertIsNotNone(config_.get('smtp', 'user'), config_file) + self.assertIsNotNone(config_.get('smtp', 'password'), config_file) + self.assertIsNotNone(config_.get('smtp', 'sender'), config_file) + self.assertIsNotNone(config_.get('smtp', 'receiver'), config_file) + self.assertIsNotNone(config_.get('smtp', 'starttls'), config_file) + self.assertIsNotNone(config_.get('smtp', 'smtps'), config_file) + self.assertIsNotNone(config_.get('smtp', 'verify_server_cert'), config_file) + self.assertIsNotNone(config_.get('smtp', 'ca_cert_file'), config_file) + self.assertIsNotNone(config_.get('gpg', 'required'), config_file) + self.assertIsNotNone(config_.get('gpg', 'home_dir'), config_file) + self.assertIsNotNone(config_.get('gpg', 'pub_key_file'), config_file) + self.assertIsNotNone(config_.get('ldap', 'host'), config_file) + self.assertIsNotNone(config_.get('ldap', 'port'), config_file) + self.assertIsNotNone(config_.get('ldap', 'base_dn'), config_file) + self.assertIsNotNone(config_.get('ldap', 'tls'), config_file) + self.assertIsNotNone(config_.get('ldap', 'cacert'), config_file) + self.assertIsNotNone(config_.get('logging', 'file'), config_file) + + def test_get_db_host(self): + expected_host = self.expected_config.get('database', 'host') + host = config.get_database_host() + self.assertEqual(expected_host, host) + + def test_get_db_port(self): + expected_port = self.expected_config.getint('database', 'port') + port = config.get_database_port() + self.assertEqual(expected_port, port) + + def test_get_database_name(self): + expected_db_name = self.expected_config.get('database', 'name') + db_name = config.get_database_name() + self.assertEqual(expected_db_name, db_name) + + def test_get_database_user(self): + expected_db_user = self.expected_config.get('database', 'user') + db_user = config.get_database_user() + self.assertEqual(expected_db_user, db_user) + + def test_get_database_password(self): + expected_db_password = self.expected_config.get('database', 'password') + db_password = config.get_database_password() + self.assertEqual(expected_db_password, db_password) + + def test_is_database_authentication_enabled(self): + self.assertEqual(self.get_boolean_value_for('database', 'authentication'), + config.is_database_authentication_enabled()) + + def test_get_cve_search_db_name(self): + expected_db_name = self.expected_config.get('cve-search', 'db') + db_name = config.get_cve_search_db_name() + self.assertEqual(expected_db_name, db_name) + + def test_get_cve_search_dir(self): + expected_dir = self.expected_config.get('cve-search', 'dir') + dir_ = config.get_cve_search_dir() + self.assertEqual(expected_dir, dir_) + + def test_get_cve_search_url(self): + expected_url = self.add_cve_path_to_url() + url = config.get_cve_search_url() + self.assertEqual(expected_url, url) + + def add_cve_path_to_url(self): + return urljoin(self.expected_config.get('cve-search', 'url'), '/cve/') + + def test_get_frontend_host(self): + expected_host = self.expected_config.get('frontend', 'host') + host = config.get_frontend_host() + self.assertEqual(expected_host, host) + + def test_get_frontend_port(self): + expected_port = self.expected_config.get('frontend', 'port') + port = config.get_frontend_port() + self.assertEqual(expected_port, port) + + def test_get_log_file(self): + expected_log_file = self.expected_config.get('logging', 'file') + log_file = config.get_log_file() + self.assertEqual(expected_log_file, log_file) + + def test_get_inventory_database_host(self): + expected_host = self.expected_config.get('inventory-database', 'host') + host = config.get_inventory_database_host() + self.assertEqual(expected_host, host) + + def test_get_inventory_database_user(self): + expected_user = self.expected_config.get('inventory-database', 'user') + user = config.get_inventory_database_user() + self.assertEqual(expected_user, user) + + def test_get_glpi_db_mysql_password(self): + expected_password = self.expected_config.get('inventory-database', 'password') + password = config.get_inventory_database_password() + self.assertEqual(expected_password, password) + + def test_get_glpi_db_name(self): + expected_db_name = self.expected_config.get('inventory-database', 'name') + db_name = config.get_glpi_db_name() + self.assertEqual(expected_db_name, db_name) + + def test_get_smpt_server_host(self): + expected_host = self.expected_config.get('smtp', 'host') + host = config.get_smtp_server_host() + self.assertEqual(expected_host, host) + + def test_get_smpt_server_port(self): + expected_port = self.expected_config.get('smtp', 'port') + port = config.get_smtp_server_port() + self.assertEqual(expected_port, port) + + def test_get_smtp_user(self): + expected_user = self.expected_config.get('smtp', 'user') + user = config.get_smtp_user() + self.assertEqual(expected_user, user) + + def test_get_smtp_password(self): + expected_password = self.expected_config.get('smtp', 'password') + password = config.get_smtp_password() + self.assertEqual(expected_password, password) + + def test_get_smtp_sender(self): + expected_sender = self.expected_config.get('smtp', 'sender') + sender = config.get_smtp_sender() + self.assertEqual(expected_sender, sender) + + def test_get_smtp_receiver(self): + expected_receiver = self.expected_config.get('smtp', 'receiver') + receiver = config.get_smtp_receiver() + self.assertEqual(expected_receiver, receiver) + + def test_is_smtp_starttls_enabled(self): + self.assertEqual(self.get_boolean_value_for('smtp', 'starttls'), config.is_smtp_starttls_enabled()) + + def test_is_smtps_enabled(self): + self.assertEqual(self.get_boolean_value_for('smtp', 'smtps'), config.is_smtps_enabled()) + + def test_verify_smtp_server_cert(self): + self.assertEqual(self.get_boolean_value_for('smtp', 'verify_server_cert'), config.is_verify_smtp_server_cert_enabled()) + + def test_get_smtp_ca_cert_file(self): + self.assertEqual(self.expected_config.get('smtp', 'ca_cert_file'), config.get_smtp_ca_cert_file()) + + def test_is_gpg_encryption_enabled(self): + self.assertEqual(self.get_boolean_value_for('gpg', 'required'), config.is_gpg_encryption_enabled()) + + def get_gpg_home_dir(self): + self.assertEqual(self.expected_config.get('gpg', 'home_dir'), config.get_gpg_home_dir()) + + def get_gpg_pub_key_file(self): + self.assertEqual(self.expected_config.get('gpg', 'pub_key_file'), config.get_gpg_pub_key_file()) + + def test_get_ldap_host(self): + expected_host = self.expected_config.get('ldap', 'host') + host = config.get_ldap_host() + self.assertEqual(expected_host, host) + + def test_get_ldap_port(self): + expected_port = self.expected_config.getint('ldap', 'port') + port = config.get_ldap_port() + self.assertEqual(expected_port, port) + + def test_get_ldap_base_dn(self): + expected_base_dn = self.expected_config.get('ldap', 'base_dn') + base_dn = config.get_ldap_base_dn() + self.assertEqual(expected_base_dn, base_dn) + + def test_is_ldap_tls_enabled(self): + self.assertEqual(self.get_boolean_value_for('ldap', 'tls'), config.is_ldap_tls_enabled()) + + def test_get_ldap_cacert_file_path(self): + expected_ca_cert = self.expected_config.get('ldap', 'cacert') + ca_cert = config.get_ldap_cacert_file_path() + self.assertEqual(expected_ca_cert, ca_cert) + + def get_boolean_value_for(self, block, option): + try: + return self.expected_config.getboolean(block, option) + except ValueError: + return False + + def load_configuration(self, config_file): + config_ = configparser.RawConfigParser() + config_.read(config_file) + return config_ + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_cpe_matching/__init__.py b/tests/test_cpe_matching/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_cpe_matching/test_cpe_matcher.py b/tests/test_cpe_matching/test_cpe_matcher.py new file mode 100644 index 0000000..a74ffb2 --- /dev/null +++ b/tests/test_cpe_matching/test_cpe_matcher.py @@ -0,0 +1,66 @@ +import unittest +from copy import copy +from pymongo import MongoClient +from local_repositories.cpe_dict import IVA_CPE_COLLECTION +from tests.mock_config import * + +CPE1 = {'wfn': {'part': 'a', 'vendor': '1024cms', 'product': '1024_cms', 'version': '0.7', 'update': 'ANY', + 'edition': 'ANY', 'sw_edition': 'ANY', 'target_sw': 'ANY', 'target_hw': 'ANY', 'other': 'ANY', + 'language': 'ANY'}, + 'uri_binding': 'cpe:/a:1024cms:1024_cms:0.7', + 'formatted_string_binding': 'cpe:2.3:a:1024cms:1024_cms:0.7:*:*:*:*:*:*:*'} + +CPE2 = {'wfn': {'part': 'a', 'vendor': '1024cms', 'product': '1024_cms', 'version': '1.7', 'update': 'ANY', 'edition': 'ANY', + 'sw_edition': 'ANY', 'target_sw': 'ANY', 'target_hw': 'ANY', 'other': 'ANY', 'language': 'ANY'}, + 'uri_binding': 'cpe:/a:1024cms:1024_cms:1.7', + 'formatted_string_binding': 'cpe:2.3:a:1024cms:1024_cms:1.7:*:*:*:*:*:*:*'} + +CPE3 = {'wfn': {'part': 'a', 'vendor': 'cms_vendor', + 'product': '1024_cms', 'version': '1.7', 'update': 'ANY', 'edition': 'ANY', + 'sw_edition': 'ANY', 'target_sw': 'ANY', 'target_hw': 'ANY', 'other': 'ANY', 'language': 'ANY'}, + 'uri_binding': 'cpe:/a:cms_vendor:1024_cms:1.7', + 'formatted_string_binding': 'cpe:2.3:a:cms_vendor:1024_cms:1.7:*:*:*:*:*:*:*'} +CPE4 = {'wfn': {'part': 'a', 'vendor': 'microsoft', + 'product': '.net_framework', 'version': '1.7', 'update': 'ANY', 'edition': 'ANY', + 'sw_edition': 'ANY', 'target_sw': 'windows', 'target_hw': 'x64', 'other': 'ANY', 'language': 'ANY'}, + 'uri_binding': 'cpe:/a:microsoft:.net_framework:1.7', + 'formatted_string_binding': 'cpe:2.3:a:microsoft:.net_framework:1.7:*:*:*:*:*:*:*'} +EXPECTED_CPE_MATCHES = [CPE1, CPE2, CPE3] +SOFTWARE_1 = {'id': 'd6218a56203853300f4862ae8c23a103', 'product': '1024cms 1024-cms', 'vendor': '1024CMS Corporation', 'version': '0.7'} +SOFTWARE_2 = {'id': 'd6218a56203853300f4862ae8c23a103', 'product': 'Product Not Exist', 'vendor': 'Vendor Unknown', 'version': '0'} +SOFTWARE_3 = {'id': 'd6218a56203853300f4862ae8c23a103', 'product': '1024cms0', 'vendor': 'cms_vendor_ld', 'version': '0'} + + +class TestCPEMatcher(unittest.TestCase): + + def setUp(self): + self.mongodb_client = MongoClient(DB_HOST, DB_PORT) + self.test_db = self.mongodb_client[IVA_DB_NAME] + self.test_db_collection = self.test_db[IVA_CPE_COLLECTION] + self.test_db_collection.insert_many(documents=[copy(CPE1), copy(CPE2), copy(CPE3), copy(CPE4)]) + self.create_cpe_matcher_obj() + + def create_cpe_matcher_obj(self): + self.cpe_matcher = patch_config_for('matching.cpe_matcher', 'CPEMatcher') + + def test_get_cpe_matches_for_software(self): + cpe_matches = self.cpe_matcher.search_cpes_for_software(SOFTWARE_1) + self.assertEqual(2, len(cpe_matches)) + self.assertEqual(EXPECTED_CPE_MATCHES[0], cpe_matches[0]) + self.assertEqual(EXPECTED_CPE_MATCHES[1], cpe_matches[1]) + + def test_get_cpe_matches_returns_no_matches(self): + cpe_matches = self.cpe_matcher.search_cpes_for_software(SOFTWARE_2) + self.assertEqual(0, len(cpe_matches)) + + def test_edit_distance_in_product_and_vendor(self): + cpe_matches = self.cpe_matcher.search_cpes_for_software(SOFTWARE_3) + self.assertEqual(1, len(cpe_matches)) + self.assertEqual(CPE3, cpe_matches[0]) + + def tearDown(self): + self.mongodb_client.drop_database(IVA_DB_NAME) + self.mongodb_client.close() + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/tests/test_cpe_matching/test_cpe_matcher_2.py b/tests/test_cpe_matching/test_cpe_matcher_2.py new file mode 100644 index 0000000..7c1615c --- /dev/null +++ b/tests/test_cpe_matching/test_cpe_matcher_2.py @@ -0,0 +1,62 @@ +import unittest + +from pymongo import MongoClient +from local_repositories.cpe_dict import IVA_CPE_COLLECTION +from tests.mock_config import patch_config_for, DB_HOST, DB_PORT, IVA_DB_NAME + +CPE1 = {"uri_binding" : "cpe:/a:microsoft:visual_foxpro:9.0:sp1", "formatted_string_binding" : "cpe:2.3:a:microsoft:visual_foxpro:9.0:sp1", "wfn" : { "part" : "a", "product" : "visual_foxpro", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "sp1", "vendor" : "microsoft", "edition" : "ANY", "version" : "9.0", "other" : "ANY", "target_hw" : "ANY" } } +CPE2 = {"uri_binding" : "cpe:/a:microsoft:visual_studio:97", "formatted_string_binding" : "cpe:2.3:a:microsoft:visual_studio:97", "wfn" : {"part" : "a", "product" : "visual_studio", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "ANY", "vendor" : "microsoft", "edition" : "ANY", "version" : "97", "other" : "ANY", "target_hw" : "ANY"}} +CPE3 = {"uri_binding" : "cpe:/a:microsoft:visual_c%2b%2b:-", "formatted_string_binding" : "cpe:2.3:a:microsoft:visual_c%2b%2b", "wfn" : {"part" : "a", "product" : "visual_c++", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "ANY", "vendor" : "microsoft", "edition" : "ANY", "version" : "NA", "other" : "ANY", "target_hw" : "ANY"}} +CPE4 = {"uri_binding" : "cpe:/a:microsoft:visual_c%2b%2b:2002", "formatted_string_binding" : "cpe:2.3:a:microsoft:visual_c%2b%2b:2002", "wfn" : {"part" : "a", "product" : "visual_c++", "sw_edition" : "ANY", "target_sw" : "ANY", "language" : "ANY", "update" : "ANY", "vendor" : "microsoft", "edition" : "ANY", "version" : "2002", "other" : "ANY", "target_hw" : "ANY"}} +CPE5 = {"uri_binding" : "cpe:/a:microsoft:visual_c%23:-", "formatted_string_binding" : "cpe:2.3:a:microsoft:visual_c%23", "wfn" : { "vendor" : "microsoft", "product" : "visual_c#", "sw_edition" : "ANY", "target_hw" : "ANY", "update" : "ANY", "edition" : "ANY", "other" : "ANY", "target_sw" : "ANY", "language" : "ANY", "part" : "a", "version" : "NA" } } +CPE6 = {"uri_binding" : "cpe:/a:oracle:jre:1.4.2_38", "wfn" : { "product" : "jre", "edition" : "ANY", "part" : "a", "sw_edition" : "ANY", "target_hw" : "ANY", "other" : "ANY", "language" : "ANY", "version" : "1.4.2_38", "update" : "ANY", "vendor" : "oracle", "target_sw" : "ANY" }, "formatted_string_binding" : "cpe:2.3:a:oracle:jre:1.4.2_38" } +CPE7 = {"uri_binding" : "cpe:/a:oracle:jre:1.5.0:update_40", "wfn" : { "product" : "jre", "edition" : "ANY", "part" : "a", "sw_edition" : "ANY", "target_hw" : "ANY", "other" : "ANY", "language" : "ANY", "version" : "1.5.0", "update" : "update_40", "vendor" : "oracle", "target_sw" : "ANY" }, "formatted_string_binding" : "cpe:2.3:a:oracle:jre:1.5.0:update_40" } +CPE8 = {"uri_binding" : "cpe:/a:oracle:jdk:1.4.2_40", "wfn" : { "product" : "jdk", "edition" : "ANY", "part" : "a", "sw_edition" : "ANY", "target_hw" : "ANY", "other" : "ANY", "language" : "ANY", "version" : "1.4.2_40", "update" : "ANY", "vendor" : "oracle", "target_sw" : "ANY" }, "formatted_string_binding" : "cpe:2.3:a:oracle:jdk:1.4.2_40" } +CPE9 = {"uri_binding" : "cpe:/a:oracle:jdk:1.5.0:update_36", "wfn" : { "product" : "jdk", "edition" : "ANY", "part" : "a", "sw_edition" : "ANY", "target_hw" : "ANY", "other" : "ANY", "language" : "ANY", "version" : "1.5.0", "update" : "update_36", "vendor" : "oracle", "target_sw" : "ANY" }, "formatted_string_binding" : "cpe:2.3:a:oracle:jdk:1.5.0:update_36" } + +SOFTWARE = {"version": "9.0.21022", "product" : "Microsoft Visual C++ 2008 Redistributable - x86 9.0.21022", "id" : "f7b5ca72b77093679e0832a748a38620", "cpe" : None, "vendor" : "Microsoft Corporation", "cve_matches" : [ ] } +SOFTWARE_JRE = {"id": "c32db0ee26a06a438538a157fddeb909", "vendor" : "Oracle Corporation", "product" : "Java 8 Update 112", "cpe" : None, "cve_matches" : [ ], "version" : "8.0.1120.15" } +SOFTWARE_JDK = {"id": "2609fe77e378a4396a82282fe51696de", "vendor" : "Oracle Corporation", "product" : "Java SE Development Kit 8 Update 112", "cpe" : None, "cve_matches" : [ ], "version" : "8.0.1120.15" } +SOFTWARE_JAVA_UPDATER = {"id": "3a850a96a1c11a1e8756f743dd0ec57c", "vendor" : "Oracle Corporation", "product" : "Java Auto Updater", "cpe" : None, "cve_matches" : [ ], "version" : "2.8.121.13" } + + +class TestCPEMatcher2(unittest.TestCase): + + def setUp(self): + self.mongodb_client = MongoClient(DB_HOST, DB_PORT) + self.test_db = self.mongodb_client[IVA_DB_NAME] + self.test_db_collection = self.test_db[IVA_CPE_COLLECTION] + self.test_db_collection.insert_many(documents=[CPE1, CPE2, CPE3, CPE4, CPE5, CPE6, CPE7, CPE8, CPE9]) + self.create_cpe_matcher_obj() + + def create_cpe_matcher_obj(self): + self.cpe_matcher = patch_config_for('matching.cpe_matcher', 'CPEMatcher') + + def test_search_cpe_candidates_for_software(self): + cpe_candidates = self.cpe_matcher.search_cpes_for_software(SOFTWARE) + self.assertEqual(3, len(cpe_candidates)) + self.assertEqual(CPE3.get('uri_binding'), cpe_candidates[0].get('uri_binding')) + self.assertEqual(CPE4.get('uri_binding'), cpe_candidates[1].get('uri_binding')) + self.assertEqual(CPE5.get('uri_binding'), cpe_candidates[2].get('uri_binding')) + + def test_search_cpe_candidates_for_jre(self): + cpe_candidates = self.cpe_matcher.search_cpes_for_software(SOFTWARE_JRE) + self.assertEqual(2, len(cpe_candidates)) + self.assertEqual(CPE6.get('uri_binding'), cpe_candidates[0].get('uri_binding')) + self.assertEqual(CPE7.get('uri_binding'), cpe_candidates[1].get('uri_binding')) + + def test_search_cpe_candidates_for_jdk(self): + cpe_candidates = self.cpe_matcher.search_cpes_for_software(SOFTWARE_JDK) + self.assertEqual(4, len(cpe_candidates)) + self.assertEqual(CPE8.get('uri_binding'), cpe_candidates[0].get('uri_binding')) + self.assertEqual(CPE9.get('uri_binding'), cpe_candidates[1].get('uri_binding')) + self.assertEqual(CPE6.get('uri_binding'), cpe_candidates[2].get('uri_binding')) + self.assertEqual(CPE7.get('uri_binding'), cpe_candidates[3].get('uri_binding')) + + def tearDown(self): + self.mongodb_client.drop_database(IVA_DB_NAME) + self.mongodb_client.close() + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_cpe_matching/test_cpe_matcher_utils.py b/tests/test_cpe_matching/test_cpe_matcher_utils.py new file mode 100644 index 0000000..cd69baa --- /dev/null +++ b/tests/test_cpe_matching/test_cpe_matcher_utils.py @@ -0,0 +1,41 @@ +import re +import unittest + +from matching.cpe_matcher_utils import create_regex, add_matches_to_list + +special_chars = ['.', '^', '$', '*', '+', '?', '{', '}', '[', ']', '\\', '|', '(', ')'] +regex_start = '^' +regex_end = '.*' + + +class TestCPEMatcherUtils(unittest.TestCase): + + def test_create_regex_for_search_text_when_text_has_special_chars(self): + for especial_char in special_chars: + search_text = especial_char + 'search_text' + especial_char + self.assertEqual(self.get_expected_regex(search_text), create_regex(search_text)) + + def test_add_matches_to_list(self): + matches = ['match_1', 'match_2', 'match_1', 'match_3'] + list_ = ['match_1', 'match_4'] + add_matches_to_list(matches, list_) + self.assertEqual(4, len(list_)) + self.assertEqual('match_1', list_[0]) + self.assertEqual('match_2', list_[2]) + self.assertEqual('match_3', list_[3]) + self.assertEqual('match_4', list_[1]) + + def test_add_matches_to_list_when_matches_list_empty(self): + matches = [] + list_ = ['match_1', 'match_4'] + add_matches_to_list(matches, list_) + self.assertEqual(2, len(list_)) + self.assertEqual('match_1', list_[0]) + self.assertEqual('match_4', list_[1]) + + def get_expected_regex(self, search_text_with_special_chars): + return regex_start + re.escape(search_text_with_special_chars) + regex_end + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_cpe_matching/test_cpe_set_operations.py b/tests/test_cpe_matching/test_cpe_set_operations.py new file mode 100644 index 0000000..a567c23 --- /dev/null +++ b/tests/test_cpe_matching/test_cpe_set_operations.py @@ -0,0 +1,58 @@ +import unittest +from matching import cpe_set_operations + +cpe1 = {'wfn': {'part': 'a', 'vendor': '1024cms', 'product': '1024_cms', 'version': '0.7', 'update': 'ANY', + 'edition': 'ANY', 'sw_edition': 'ANY', 'target_sw': 'ANY', 'target_hw': 'ANY', 'other': 'ANY', + 'language': 'ANY'}, + 'uri_binding': 'cpe:/a:1024cms:1024_cms:0.7', + 'formatted_string_binding': 'cpe:2.3:a:1024cms:1024_cms:0.7:*:*:*:*:*:*:*'} + +cpe2 = {'wfn': {'part': 'a', 'vendor': '1024cms', 'product': '1024_cms', 'version': '1.7', 'update': 'ANY', 'edition': 'ANY', + 'sw_edition': 'ANY', 'target_sw': 'ANY', 'target_hw': 'ANY', 'other': 'ANY', 'language': 'ANY'}, + 'uri_binding': 'cpe:/a:1024cms:1024_cms:1.7', + 'formatted_string_binding': 'cpe:2.3:a:1024cms:1024_cms:1.7:*:*:*:*:*:*:*'} + +cpe3 = {'wfn': {'part': 'a', 'vendor': 'cms_vendor', + 'product': '1024_cms', 'version': '1.7', 'update': 'ANY', 'edition': 'ANY', + 'sw_edition': 'ANY', 'target_sw': 'ANY', 'target_hw': 'ANY', 'other': 'ANY', 'language': 'ANY'}, + 'uri_binding': 'cpe:/a:cms_vendor:1024_cms:1.7', + 'formatted_string_binding': 'cpe:2.3:a:cms_vendor:1024_cms:1.7:*:*:*:*:*:*:*'} + +cpe4 = {'wfn': {'part': 'a', 'vendor': 'microsoft', + 'product': '.net_framework', 'version': '1.7', 'update': 'ANY', 'edition': 'ANY', + 'sw_edition': 'ANY', 'target_sw': 'windows', 'target_hw': 'x64', 'other': 'ANY', 'language': 'ANY'}, + 'uri_binding': 'cpe:/a:microsoft:.net_framework:1.7', + 'formatted_string_binding': 'cpe:2.3:a:microsoft:.net_framework:1.7:*:*:*:*:*:*:*'} + +cpes_list_a = [cpe1, cpe2, cpe4] +cpes_list_b = [cpe1, cpe3, cpe4] +cpes_list_c = [cpe3] + + +class TestCPESetOperations(unittest.TestCase): + + def test_calculate_intersection_between_two_cpe_lists(self): + intersection_set = cpe_set_operations.calculate_intersection_between_two_cpe_lists(cpes_list_a, cpes_list_b) + self.assertEqual(2, len(intersection_set)) + self.assertTrue(cpe1 in intersection_set) + self.assertFalse(cpe2 in intersection_set) + self.assertFalse(cpe3 in intersection_set) + self.assertTrue(cpe4 in intersection_set) + + intersection_set = cpe_set_operations.calculate_intersection_between_two_cpe_lists(cpes_list_c, cpes_list_b) + self.assertEqual(1, len(intersection_set)) + self.assertTrue(cpe3 in intersection_set) + + intersection_set = cpe_set_operations.calculate_intersection_between_two_cpe_lists(cpes_list_c, cpes_list_a) + self.assertEqual(0, len(intersection_set)) + + def test_calculate_symmetric_difference_between_two_cpe_lists(self): + difference_set = cpe_set_operations.calculate_symmetric_difference_between_two_cpe_lists(cpes_list_a, cpes_list_b) + self.assertEqual(2, len(difference_set)) + self.assertFalse(cpe1 in difference_set) + self.assertTrue(cpe2 in difference_set) + self.assertTrue(cpe3 in difference_set) + self.assertFalse(cpe4 in difference_set) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_cpe_matching/test_cpe_sorter.py b/tests/test_cpe_matching/test_cpe_sorter.py new file mode 100644 index 0000000..7c457d0 --- /dev/null +++ b/tests/test_cpe_matching/test_cpe_sorter.py @@ -0,0 +1,103 @@ +import unittest + +from matching.cpe_sorter import * + +unsorted_cpes = [{'wfn': {'version': '4.0', 'target_sw': 'android_marshmallow'}, + 'uri_binding': 'cpe:/a:string_value_with\:double_points:internet_explorer:4.0:beta:~~~android_marshmallow~~'}, + {'wfn': {'version': '1.0.1.2', 'target_sw': 'android_marshmallow'}, + 'uri_binding': 'cpe:/a:string_value_with\:double_points:internet_explorer:1.0.1.2:beta'}, + {'wfn': {'version': '4.1.2', 'target_sw': 'ANY'}, + 'uri_binding': 'cpe:/a:string_value_with\:double_points:internet_explorer:4.1.2:beta'}, + {'wfn': {'version': '4.6.3', 'target_sw': 'windows'}, + 'uri_binding': 'cpe:/a:string_value_with\:double_points:internet_explorer:4.6.3:beta:~~~windows~~'}, + {'wfn': {'version': '4.7.1', 'target_sw': 'android'}, + 'uri_binding': 'cpe:/a:string_value_with\:double_points:internet_explorer:4.7.1:beta:~~~android~~'}, + {'wfn': {'version': '4.7.2', 'target_sw': 'ANY'}, + 'uri_binding':'cpe:/a:string_value_with\:double_points:internet_explorer:4.7.2:beta'}, + {'wfn': {'version': '4.3.2', 'target_sw': 'linux'}, + 'uri_binding': 'cpe:/a:string_value_with\:double_points:internet_explorer:4.3.2:beta:~~~linux~~'}, + {'wfn': {'version': '2.3.1', 'target_sw': 'linux'}, + 'uri_binding': 'cpe:/a:string_value_with\:double_points:internet_explorer:2.3.1:beta'}, + {'wfn': {'version': '4.7.3', 'target_sw': 'mac_os_x'}, + 'uri_binding': 'cpe:/a:string_value_with\:double_points:internet_explorer:4.7.3:beta:~~~mac_os_x~~'} + ] + + +unsorted_cpes_year = [{'wfn': {'version': '2000', 'target_sw': 'android_marshmallow'}, + 'uri_binding': 'cpe:/a:string_value_with\:double_points:internet_explorer:2000:beta:~~~android_marshmallow~~'}, + {'wfn': {'version': '2007', 'target_sw': 'android_marshmallow'}, + 'uri_binding': 'cpe:/a:string_value_with\:double_points:internet_explorer:2007:beta'}, + {'wfn': {'version': '4.1.2', 'target_sw': 'ANY'}, + 'uri_binding': 'cpe:/a:string_value_with\:double_points:internet_explorer:4.1.2:beta'}, + {'wfn': {'version': '2010', 'target_sw': 'windows'}, + 'uri_binding': 'cpe:/a:string_value_with\:double_points:internet_explorer:2010:beta:~~~windows~~'}, + {'wfn': {'version': '4.7.1', 'target_sw': 'android'}, + 'uri_binding': 'cpe:/a:string_value_with\:double_points:internet_explorer:4.7.1:beta:~~~android~~'}, + {'wfn': {'version': '2001', 'target_sw': 'ANY'}, + 'uri_binding':'cpe:/a:string_value_with\:double_points:internet_explorer:2001:beta'}, + {'wfn': {'version': '4.3.2', 'target_sw': 'linux'}, + 'uri_binding': 'cpe:/a:string_value_with\:double_points:internet_explorer:4.3.2:beta:~~~linux~~'}, + {'wfn': {'version': '2010', 'target_sw': 'linux'}, + 'uri_binding': 'cpe:/a:string_value_with\:double_points:internet_explorer:2010:beta'}, + {'wfn': {'version': '4.7.3', 'target_sw': 'mac_os_x'}, + 'uri_binding': 'cpe:/a:string_value_with\:double_points:internet_explorer:4.7.3:beta:~~~mac_os_x~~'}, + {'wfn': {'version': '2010', 'target_sw': 'mac_os_x'}, + 'uri_binding': 'cpe:/a:string_value_with\:double_points:internet_explorer:2010:beta:~~~mac_os_x~~'}] + +version = '4.7.2' +version_without_points = '4_7-2' +version_year = '2010' + +os_windows = 'windows_7' +os_linux = 'linux_ubuntu' +os_android = 'android' +os_mac = 'mac_os_x_10.11' + + +class TestCPESorter(unittest.TestCase): + + def test_sort_cpes_by_software_version(self): + sorted_cpes = sort_cpes_by_version(unsorted_cpes, version) + + self.assertEqual(len(unsorted_cpes), len(sorted_cpes)) + self.assertEqual(unsorted_cpes[5], sorted_cpes[0]) # 4.7.2 + self.assertEqual(unsorted_cpes[4], sorted_cpes[1]) # 4.7.1 + self.assertEqual(unsorted_cpes[8], sorted_cpes[2]) # 4.7.3 + self.assertEqual(unsorted_cpes[0], sorted_cpes[3]) # 4.0 + self.assertEqual(unsorted_cpes[2], sorted_cpes[4]) # 4.1.2 + self.assertEqual(unsorted_cpes[3], sorted_cpes[5]) # 4.6.3 + self.assertEqual(unsorted_cpes[6], sorted_cpes[6]) # 4.3.2 + + def test_cpes_and_sorted_cpes_are_equal_when_software_version_not_splitted_by_points(self): + sorted_cpes = sort_cpes_by_version(unsorted_cpes, version_without_points) + self.assertListEqual(unsorted_cpes, sorted_cpes) + + def test_sort_cpes_by_version_with_year(self): + sorted_cpes = sort_cpes_by_version(unsorted_cpes_year, version_year) + self.assertEqual(len(unsorted_cpes_year), len(sorted_cpes)) + self.assertEqual(unsorted_cpes_year[3], sorted_cpes[0]) # 2010 + self.assertEqual(unsorted_cpes_year[7], sorted_cpes[1]) # 2010 + self.assertEqual(unsorted_cpes_year[9], sorted_cpes[2]) # 2010 + self.assertEqual(unsorted_cpes_year[0], sorted_cpes[3]) # 2000 + self.assertEqual(unsorted_cpes_year[1], sorted_cpes[4]) # 2007 + self.assertEqual(unsorted_cpes_year[5], sorted_cpes[5]) # 2001 + + def test_sort_cpes_by_operating_system_windows(self): + sorted_cpes = sort_cpes_by_operating_system(unsorted_cpes, os_windows) + self.assertEqual(len(unsorted_cpes), len(sorted_cpes)) + self.assertEqual(unsorted_cpes[3], sorted_cpes[0]) + + def test_sort_cpes_by_operating_system_linux(self): + sorted_cpes = sort_cpes_by_operating_system(unsorted_cpes, os_linux) + self.assertEqual(len(unsorted_cpes), len(sorted_cpes)) + self.assertEqual(unsorted_cpes[6], sorted_cpes[0]) + + def test_sort_cpes_by_operating_system_android(self): + sorted_cpes = sort_cpes_by_operating_system(unsorted_cpes, os_android) + self.assertEqual(len(unsorted_cpes), len(sorted_cpes)) + self.assertEqual(unsorted_cpes[4], sorted_cpes[0]) + self.assertEqual(unsorted_cpes[0], sorted_cpes[1]) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_cpe_matching/test_search_phrases_generator.py b/tests/test_cpe_matching/test_search_phrases_generator.py new file mode 100644 index 0000000..ed54fd5 --- /dev/null +++ b/tests/test_cpe_matching/test_search_phrases_generator.py @@ -0,0 +1,87 @@ +import unittest + +from matching import search_terms_generator + +vendor = 'microsoft_corporation_' + + +class TestSearchPhrasesGenerator(unittest.TestCase): + + def test_generate_search_phrases_for_product_returns_empty_list(self): + product = '' + phrases = search_terms_generator.generate_product_search_terms(product, vendor) + self.assertEqual(len(phrases), 0) + + def test_generate_search_phrases_returns_one_phrase(self): + product = 'product' + phrases = search_terms_generator.generate_product_search_terms(product, vendor) + self.assertEqual(len(phrases), 1) + self.assertEqual(product, phrases[0]) + + def test_generate_search_phrases_for_product_returns_three_phrases(self): + product = 'first_second' + phrases = search_terms_generator.generate_product_search_terms(product, vendor) + self.assertEqual(len(phrases), 3) + self.assertEqual('first_second', phrases[0]) + self.assertEqual('second', phrases[1]) + self.assertEqual('first', phrases[2]) + + def test_generate_search_phrases_for_product_returns_five_phrases(self): + product = 'first_second_third' + phrases = search_terms_generator.generate_product_search_terms(product, vendor) + self.assertEqual(len(phrases), 5) + self.assertEqual('first_second_third', phrases[0]) + self.assertEqual('first_second', phrases[1]) + self.assertEqual('second', phrases[2]) + + def test_generate_search_phrases_for_product_returns_seven_phrases(self): + product = 'first_second_third_fourthfourthfourth' + phrases = search_terms_generator.generate_product_search_terms(product, vendor) + self.assertEqual(len(phrases), 7) + self.assertEqual('first_second_third_fourthfourthfourth', phrases[0]) + self.assertEqual('first_second_third', phrases[1]) + self.assertEqual('first_second', phrases[2]) + self.assertEqual('fourthfourthfourth', phrases[3]) + self.assertEqual('second', phrases[4]) + self.assertEqual('third', phrases[5]) + self.assertEqual('first', phrases[6]) + + def test_generate_search_phrases_for_product_does_not_excludes_vendor(self): + product = 'first_second_third_microsoft' + phrases = search_terms_generator.generate_product_search_terms(product, vendor) + self.assertEqual(len(phrases), 7) + self.assertTrue('first_second_third_microsoft' in phrases) + self.assertTrue('microsoft' in phrases) + + def test_generate_search_phrases_for_product_excludes_phrases_smaller_than_three_chars(self): + product = 'first_2_second_third_microsoft_lt_123' + phrases = search_terms_generator.generate_product_search_terms(product, vendor) + self.assertEqual(len(phrases), 13) + self.assertFalse('lt' in phrases) + self.assertFalse('2' in phrases) + self.assertEqual('first_2_second_third_microsoft_lt_123', phrases[0]) + self.assertEqual('first_2_second_third_microsoft_lt', phrases[1]) + self.assertEqual('first_2_second_third_microsoft', phrases[2]) + self.assertEqual('first_2_second_third_lt_123', phrases[3]) + self.assertEqual('first_2_second_third_lt', phrases[4]) + self.assertEqual('first_2_second_third', phrases[5]) + self.assertEqual('first_2_second', phrases[6]) + self.assertEqual('first_2', phrases[7]) + self.assertEqual('microsoft', phrases[8]) + self.assertEqual('second', phrases[9]) + self.assertEqual('123', phrases[12]) + + def test_remove_version_from_product_search_phrases(self): + version = '4.5.2' + search_phrases = ['.net_framework', version] + search_phrases = search_terms_generator.remove_version_from_search_terms(search_phrases, version) + self.assertEqual(len(search_phrases), 1) + self.assertFalse(list(search_phrases).__contains__(version)) + + search_phrases = ['.net_framework', '4.0'] + search_phrases = search_terms_generator.remove_version_from_search_terms(search_phrases, version) + self.assertEqual(len(search_phrases), 2) + self.assertTrue(list(search_phrases).__contains__('4.0')) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_cpe_matching/test_software_formatter.py b/tests/test_cpe_matching/test_software_formatter.py new file mode 100644 index 0000000..b889b90 --- /dev/null +++ b/tests/test_cpe_matching/test_software_formatter.py @@ -0,0 +1,47 @@ +import unittest +from matching import software_formatter +from matching.software_formatter import FormattedSoftware + +PROD_SEARCH_TERMS_NO_VER = ['microsoft', 'office', '201'] +PROD_SEARCH_TERMS = ['microsoft', 'office', '2010'] +UNFORMATTED_SW = {'product': 'Microsoft .NET Framework 4.5.2', 'vendor': 'Microsoft Corporation', + 'version': '4.5.51209', 'os': 'Windows'} + + +class TestInventoryItem(unittest.TestCase): + + def setUp(self): + self.formatted_software = FormattedSoftware(UNFORMATTED_SW) + + def test_get_product(self): + self.assertEqual('microsoft_.net_framework_4.5.2', self.formatted_software.product) + + def test_get_vendor(self): + self.assertEqual('microsoft_corporation', self.formatted_software.vendor) + + def test_get_os(self): + self.assertEqual('Windows', self.formatted_software.os) + + def test_get_version(self): + self.assertEqual('4.5.51209', self.formatted_software.get_version(PROD_SEARCH_TERMS)) + + def test_get_version_when_sw_version_is_empty(self): + formatted_sw = FormattedSoftware({'product': 'Microsoft Office 2010', 'vendor': 'Microsoft Corporation', + 'version': '', 'os': 'windows'}) + self.assertEqual('2010', formatted_sw.get_version(PROD_SEARCH_TERMS)) + + formatted_sw = FormattedSoftware({'product': 'Microsoft Office 201', 'vendor': 'Microsoft Corporation', + 'version': '', 'os': 'windows'}) + self.assertEqual('', formatted_sw.get_version(PROD_SEARCH_TERMS_NO_VER)) + + def test_format_product(self): + sw_dict = {'product': 'FusionInventory Agent 2.3.17 (x86 edition)'} + self.assertEqual('fusioninventory_agent_2.3.17_x86_edition', software_formatter.format_product(sw_dict)) + + def test_format_vendor(self): + sw_dict = {'vendor': 'Microsoft Corporation_'} + self.assertEqual('microsoft_corporation', software_formatter.format_vendor(sw_dict)) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_cve_matching/__init__.py b/tests/test_cve_matching/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_cve_matching/test_cve_matcher.py b/tests/test_cve_matching/test_cve_matcher.py new file mode 100644 index 0000000..7f80ebe --- /dev/null +++ b/tests/test_cve_matching/test_cve_matcher.py @@ -0,0 +1,183 @@ +import unittest +from copy import copy +from pymongo import MongoClient +from local_repositories.cve_feeds import IVA_CVE_COLLECTION +from matching.cve_matcher import are_strings_similar, is_version_any, get_main_version +from tests.dict_tester import DictTester +from tests.mock_config import * +from inventory.inventory import INVENTORY_DB_COLLECTION + + +CVE_1 = {'cve_id': 'CVE-2017-0000', + 'cve_summary': 'Adobe Connect before 95.2 allows remote attackers to spoof the user interface.', + 'cpe_entries': [{'uri_binding': 'cpe:/a:vendor:product:9.5', 'wfn': {'part': 'a', 'vendor': 'vendor', + 'product': 'product', + 'version': '9', + 'update': 'ANY', 'edition': 'ANY', + 'language': 'ANY', 'sw_edition': 'ANY', + 'target_sw': 'ANY', + 'target_hw': 'ANY', + 'other': 'ANY'}}]} + +CVE_2 = {'cve_id': 'CVE-2017-0001', + 'cve_summary': 'Adobe Connect before 95.2 allows remote attackers to spoof the user interface.', + 'cpe_entries': [{'uri_binding': 'cpe:/a:vendor:product:9.3', 'wfn': {'part': 'a', 'vendor': 'vendor', + 'product': 'product', + 'version': '9.3', + 'update': 'ANY', 'edition': 'ANY', + 'language': 'ANY', 'sw_edition': 'ANY', + 'target_sw': 'ANY', + 'target_hw': 'ANY', + 'other': 'ANY'}}, + {'uri_binding': 'cpe:/a:no_match:no_match:9.3', 'wfn': {'part': 'a', 'vendor': 'no_match', + 'product': 'no_match', + 'version': '9.3', + 'update': 'ANY', 'edition': 'ANY', + 'language': 'ANY', 'sw_edition': 'ANY', + 'target_sw': 'ANY', + 'target_hw': 'ANY', + 'other': 'ANY'}}]} + +CVE_3 = {'cve_id': 'CVE-2017-0002', + 'cve_summary': 'ldjzeoq 73n32 435jv product jduanvctz vendor', + 'cpe_entries': []} + + +CVE_4 = {'cve_id': 'CVE-2017-0003', + 'cve_summary': 'Adobe Connect before 95.2 allows remote attackers to spoof the user interface.', + 'cpe_entries': [{'uri_binding': 'cpe:/a:adobe:reader:9.5', 'wfn': {'part': 'a', 'vendor': 'adobe', + 'product': 'reader', + 'version': '9.5', + 'update': 'ANY', 'edition': 'ANY', + 'language': 'ANY', 'sw_edition': 'ANY', + 'target_sw': 'ANY', + 'target_hw': 'ANY', + 'other': 'ANY'}}]} + + +CVE_5 = {'cve_id': 'CVE-2017-0004', + 'cve_summary': 'Adobe Connect before 95.2 allows remote attackers to spoof the user interface.', + 'cpe_entries': [{'uri_binding': 'cpe:/a:adobe:adobe_reader:9.5', 'wfn': {'part': 'a', 'vendor': 'adobe', + 'product': 'adobe_reader', + 'version': '9.5', + 'update': 'ANY', 'edition': 'ANY', + 'language': 'ANY', 'sw_edition': 'ANY', + 'target_sw': 'ANY', + 'target_hw': 'ANY', + 'other': 'ANY'}}]} + +CVE_6 = {'cve_id': 'CVE-2017-0005', + 'cve_summary': 'Adobe Connect before 95.2 allows remote attackers to spoof the user interface.', + 'cpe_entries': [{'uri_binding': 'cpe:/a:adobe:reader_adobe:9.5', 'wfn': {'part': 'a', 'vendor': 'adobe', + 'product': 'reader_adobe', + 'version': '9.5', + 'update': 'ANY', 'edition': 'ANY', + 'language': 'ANY', 'sw_edition': 'ANY', + 'target_sw': 'ANY', + 'target_hw': 'ANY', + 'other': 'ANY'}}]} + + +CVE_7 = {'cve_id': 'CVE-2017-0006', + 'cve_summary': '', + 'cpe_entries': [{'uri_binding': 'cpe:/a:adobe:reader_adobe:9.5', 'wfn': {'part': 'a', 'vendor': 'adobe', + 'product': 'reader_adobe', + 'version': '9.5.6', + 'update': 'ANY', 'edition': 'ANY', + 'language': 'ANY', 'sw_edition': 'ANY', + 'target_sw': 'ANY', + 'target_hw': 'ANY', + 'other': 'ANY'}}, + {'uri_binding': 'cpe:/a:adobe:reader_adobe:9.5', 'wfn': {'part': 'a', 'vendor': 'adobe', + 'product': 'reader_adobe', + 'version': '9.5', + 'update': 'ANY', 'edition': 'ANY', + 'language': 'ANY', 'sw_edition': 'ANY', + 'target_sw': 'ANY', + 'target_hw': 'ANY', + 'other': 'ANY'}}]} + + +class TestCVEMatcher(unittest.TestCase): + + def setUp(self): + self.dict_tester = DictTester() + self.mongodb_client = MongoClient(DB_HOST, DB_PORT) + self.test_db = self.mongodb_client[IVA_DB_NAME] + self.iva_cve_collection = self.test_db[IVA_CVE_COLLECTION] + self.iva_inventory_collection = self.test_db[INVENTORY_DB_COLLECTION] + self.insert_cves() + self.insert_sw_inventory() + self.create_cve_matcher_obj() + + def create_cve_matcher_obj(self): + self.cve_matcher = patch_config_for('matching.cve_matcher', 'CVEMatcher') + + def test_search_cves_for_cpe_returns_three_matches(self): + matches = self.cve_matcher.search_cves_for_cpe('cpe:/a:vendor:product:9.5') + self.assertEqual(3, len(matches)) + + self.assertTrue(CVE_1 in matches) + self.assertEqual(CVE_2.get('cve_id'), matches[1].get('cve_id')) + self.assertTrue(CVE_3 in matches) + self.assertEqual(1, len(matches[1].get('cpe_entries'))) + + def test_search_cves_for_cpe_returns_when_cve_cpe_product_has_vendor(self): + matches = self.cve_matcher.search_cves_for_cpe('cpe:/a:adobe:reader:9.5') + self.assertEqual(4, len(matches)) + self.assertTrue(CVE_4 in matches) + self.assertTrue(CVE_5 in matches) + self.assertTrue(CVE_6 in matches) + + def test_search_cves_for_cpe_when_cpe_product_has_vendor(self): + matches = self.cve_matcher.search_cves_for_cpe('cpe:/a:adobe:adobe_reader:9.5') + self.assertEqual(2, len(matches)) + self.assertTrue(CVE_4 in matches) + self.assertTrue(CVE_5 in matches) + + def test_search_cves_for_cpe_when_cpe_product_has_typo(self): + matches = self.cve_matcher.search_cves_for_cpe('cpe:/a:vendor:produc:9.5') + self.assertEqual(3, len(matches)) + + def insert_cves(self): + self.iva_cve_collection.insert_many(documents=[copy(CVE_1), copy(CVE_2), copy(CVE_3), copy(CVE_4), copy(CVE_5), + copy(CVE_6), copy(CVE_7)]) + + def insert_sw_inventory(self): + self.iva_inventory_collection.insert_one({'cpe': {'wfn': {'product': 'product', 'vendor': 'vendor'}}}) + self.iva_inventory_collection.insert_one({'cpe': {'wfn': {'product': 'reader', 'vendor': 'adobe'}}}) + self.iva_inventory_collection.insert_one({'cpe': {'wfn': {'product': 'adobe_reader', 'vendor': 'adobe'}}}) + + def test_compare_words_edit_distance_returns_true(self): + self.assertTrue(are_strings_similar('abcd', 'abdc')) + self.assertTrue(are_strings_similar('abcd', 'abcc')) + self.assertTrue(are_strings_similar('abcd', 'accc')) + self.assertTrue(are_strings_similar('microsoft', 'microsott')) + self.assertTrue(are_strings_similar('microsoft', 'macrosott')) + self.assertTrue(are_strings_similar('microsoft', 'macrosof')) + self.assertTrue(are_strings_similar('microsoft', 'macrosot')) + + def test_compare_words_edit_distance_returns_false(self): + self.assertFalse(are_strings_similar('abcd', 'axxx')) + self.assertFalse(are_strings_similar('abcd', 'cccc')) + self.assertFalse(are_strings_similar('abcd', 'xjkh')) + self.assertFalse(are_strings_similar('abcdabcdabcdab', 'abcdabcdabcdabcdabcdab1154ds')) + self.assertFalse(are_strings_similar('microsoft', 'macrosotx')) + + def test_is_version_any(self): + self.assertTrue(is_version_any({'version': 'ANY'})) + self.assertTrue(is_version_any({'version': '*'})) + self.assertFalse(is_version_any({'version': '15315'})) + + def test_get_main_version(self): + self.assertEqual('1', get_main_version({'version': '1.5.86'})) + self.assertEqual('2007', get_main_version({'version': '2007'})) + self.assertEqual('ANY', get_main_version({'version': 'ANY'})) + + def tearDown(self): + self.mongodb_client.drop_database(IVA_DB_NAME) + self.mongodb_client.close() + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_database.py b/tests/test_database.py new file mode 100644 index 0000000..e3992a3 --- /dev/null +++ b/tests/test_database.py @@ -0,0 +1,337 @@ +import unittest +from copy import copy +from tests.dict_tester import DictTester +from pymongo import MongoClient +from tests.mock_config import * + +COLLECTION_NAME = 'test_collection' +DOC = {'wfn': {'part': 'a', 'vendor': '$0.99_kindle_books_project', 'product': '$0.99_kindle_books', 'version': '6', + 'update': 'ANY', 'edition': 'ANY', 'sw_edition': 'ANY', 'target_sw': 'android', 'target_hw': 'ANY', + 'other': 'ANY', 'language': 'ANY'}, + 'uri_binding': 'test_cpe:/a:%240.99_kindle_books_project:%240.99_kindle_books:6::~~~android~~', + 'formatted_string_binding': 'test_cpe:2.3:a:\$0.99_kindle_books_project:\$0.99_kindle_books:6:*:*:*:*:android:*:*' + } +DOCUMENTS = [{'id': 1}, {'id': 2}, {'id': 3}, {'id': 4}, {'id': 5}, {'id': 6}, {'id': 7}, {'id': 8}, {'id': 9}, + {'id': 10}, {'id': 11}, {'id': 12}] + +CVE_1 = {'cve_id': 'CVE-2017-0004', 'cve_summary': 'blablbablablaabla.', + 'cpe_entries': [{'uri_binding': 'cpe:/a:adobe:adobe_reader:9.5', + 'wfn': {'vendor': 'adobe', 'product': 'adobe_reader'}}, + {'uri_binding': 'cpe:/a:adobe:reader:9.5', + 'wfn': {'vendor': 'adobe', 'product': 'reader'}}, + {'uri_binding': 'cpe:/a:bla:blabla:111', + 'wfn': {'vendor': 'no_adobe', 'product': 'adobe_reader'}}]} + +CVE_2 = {'cve_id': 'CVE-2017-0005', 'cve_summary': 'blablbablablaabla.', + 'cpe_entries': [{'uri_binding': 'cpe:/a:adobe:reader:4525', + 'wfn': {'vendor': 'adobe', 'product': 'reader'}}, + {'uri_binding': 'cpe:/a:bla:blabla:111', + 'wfn': {'vendor': 'no_adobe', 'product': 'adobe_reader'}}]} + +CVE_3 = {'cve_id': 'CVE-2017-0006', 'cve_summary': 'blablbablablaabla.', + 'cpe_entries': [{'uri_binding': 'cpe:/a:microsoft:ie:11', + 'wfn': {'vendor': 'microsoft', 'product': 'ie'}}, + {'uri_binding': 'cpe:/a:mozilla:firefox:40', + 'wfn': {'vendor': 'adobe', 'mozilla': 'firefox'}}]} + + +class TestDatabase(unittest.TestCase): + + def setUp(self): + # mock + self.dict_tester = DictTester() + self.mongodb_client = MongoClient(DB_HOST, DB_PORT) + self.test_db = self.mongodb_client[IVA_DB_NAME] + self.test_db_collection = self.test_db[COLLECTION_NAME] + self.create_db_object() + + def create_db_object(self): + self.db = patch_config_for('database', 'Database') + + def test_insert_doc_into_collection(self): + # insert document + self.db.insert_document_in_collection(DOC, COLLECTION_NAME) + + # verify insert + self.assertTrue(IVA_DB_NAME in self.mongodb_client.database_names(), 'Database ' + IVA_DB_NAME + ' was not created') + self.assertTrue(COLLECTION_NAME in self.test_db.collection_names(), 'Collection ' + COLLECTION_NAME + ' was not created') + self.assertIsNotNone(self.test_db_collection.find_one(DOC), 'Document ' + str(DOC) + ' was not inserted') + + def test_exist_doc_in_collection_return_true(self): + # insert document + self.test_db_collection.insert_one(DOC) + + # search document + self.assertTrue(self.db.exist_doc_in_collection({'uri_binding': DOC.get('uri_binding')}, COLLECTION_NAME)) + + def test_search_text_in_collection_with_regex(self): + # insert document + self.test_db_collection.insert_one(DOC) + + # search + regex = '^\$0\.99.*' + field = 'wfn.vendor' + search_result = self.db.search_text_with_regex_in_collection(regex, field, COLLECTION_NAME) + # verify + self.assertEqual(search_result.count(), 1) + + # search + regex = 'kindle_books$' + field = 'wfn.product' + search_result = self.db.search_text_with_regex_in_collection(regex, field, COLLECTION_NAME) + # verify + self.assertEqual(search_result.count(), 1) + + # search + regex = '^kindle_books$' + field = 'wfn.product' + search_result = self.db.search_text_with_regex_in_collection(regex, field, COLLECTION_NAME) + # verify + self.assertEqual(search_result.count(), 0) + + # search + regex = '^\$0\.99.*books$' + field = 'wfn.product' + search_result = self.db.search_text_with_regex_in_collection(regex, field, COLLECTION_NAME) + # verify + self.assertEqual(search_result.count(), 1) + + def test_search_text_in_collection_with_regex_does_not_find_matches_when_special_chars_not_escaped(self): + # insert document + self.test_db_collection.insert_one(DOC) + + # search document + regex = '^$0.99.*' + field = 'wfn.vendor' + search_result = self.db.search_text_with_regex_in_collection(regex, field, COLLECTION_NAME) + # verify + self.assertEqual(search_result.count(), 0) + + def test_exist_doc_in_collection_return_false(self): + # search document + self.assertFalse(self.db.exist_doc_in_collection({'uri_binding': DOC.get('uri_binding')}, COLLECTION_NAME)) + + def test_number_of_documents_in_collection_returns_5(self): + # insert 5 documents in collection + for i in range(5): + self.db.insert_document_in_collection({'id': i}, COLLECTION_NAME) + + # get number of documents in collection + self.assertEqual(5, self.db.get_number_of_documents_in_collection(COLLECTION_NAME)) + + def test_number_of_documents_in_collection_returns_0(self): + # get number of documents in collection + self.assertEqual(0, self.db.get_number_of_documents_in_collection(COLLECTION_NAME)) + + def test_number_of_documents_with_filter_returns_3(self): + self.db.insert_document_in_collection({'id': '1', 'status': 'new'}, COLLECTION_NAME) + self.db.insert_document_in_collection({'id': '1', 'status': 'removed'}, COLLECTION_NAME) + self.db.insert_document_in_collection({'id': '2', 'status': 'new'}, COLLECTION_NAME) + self.db.insert_document_in_collection({'id': '1', 'status': 'closed'}, COLLECTION_NAME) + self.db.insert_document_in_collection({'id': '1', 'status': 'new'}, COLLECTION_NAME) + + # get number of documents in collection + self.assertEqual(3, self.db.get_number_of_documents_in_collection(COLLECTION_NAME, {'status': 'new'})) + + def test_number_of_documents_with_filter_returns_0(self): + self.db.insert_document_in_collection({'id': '1', 'status': 'new'}, COLLECTION_NAME) + self.db.insert_document_in_collection({'id': '1', 'status': 'removed'}, COLLECTION_NAME) + self.db.insert_document_in_collection({'id': '2', 'status': 'closed'}, COLLECTION_NAME) + + # get number of documents in collection + self.assertEqual(0, self.db.get_number_of_documents_in_collection(COLLECTION_NAME, {'status': 'any'})) + + def test_update_document_in_collection(self): + # insert document + doc_to_be_updated = {'doc_id': 1, 'value': 'to be updated'} + self.test_db_collection.insert_one(doc_to_be_updated) + + # update document + filter_ = {'doc_id': 1} + update = {'value': 'updated'} + self.db.update_document_in_collection(filter_, update, COLLECTION_NAME) + + # verify update + updated_doc = self.test_db_collection.find_one(filter_) + self.assertIsNotNone(self.test_db_collection.find_one(updated_doc)) + self.assertEqual(updated_doc.get('value'), 'updated') + + def test_update_documents_in_collection(self): + # insert document + doc_to_be_updated = {'doc_id': 1, 'value': 'to be updated'} + self.test_db_collection.insert_one(doc_to_be_updated) + + docs = [{'doc_id': 1, 'value': 'updated'}, + {'doc_id': 2, 'value': 'new inserted'}] + find_filter = 'doc_id' + self.db.update_documents_in_collection(docs, find_filter, COLLECTION_NAME) + + # verify update + self.assertEqual(2, self.test_db_collection.count()) + updated_doc = self.test_db_collection.find_one({'doc_id': 1}) + self.assertEqual(updated_doc.get('value'), 'updated') + inserted_doc = self.test_db_collection.find_one({'doc_id': 2}) + self.assertEqual(inserted_doc.get('value'), 'new inserted') + + def test_update_documents_in_collection_when_doc_list_empty(self): + self.db.update_documents_in_collection([], '', COLLECTION_NAME) + + def test_search_doc_in_collection_returns_one_dict(self): + search_condition = {'test_name': 'search_doc_in_collection'} + + # insert document + self.test_db_collection.insert_one({'id_test': '1', 'test_name': 'search_doc_in_collection'}) + + # search document + dict_ = self.db.search_document_in_collection(search_condition, COLLECTION_NAME) + + # verify + self.dict_tester.assertEqualKeys({'id_test': '1', 'test_name': 'search_doc_in_collection'}, dict_) + self.dict_tester.assertEqualValues({'id_test': '1', 'test_name': 'search_doc_in_collection'}, dict_) + + def test_search_doc_in_collection_returns_one_dict_without_id_attribute(self): + search_condition = {'test_name': 'search_doc_in_collection'} + expected_dict = {'i': '1', 'test_name': 'search_doc_in_collection'} + + # insert document + self.test_db_collection.insert_one(expected_dict) + + # search document + dict_ = self.db.search_document_in_collection(search_condition, COLLECTION_NAME) + + # verify + self.assertFalse('_id' in dict_) + + def test_search_doc_with_array_in_collection_returns_one_dict(self): + search_condition = {'tests': {'$in': ['test2']}} + expected_dict = {'id_test': '1', 'tests': ['search_doc_in_collection', 'test2']} + + # insert document + self.test_db_collection.insert_one(copy(expected_dict)) + + # search document + dict_ = self.db.search_document_in_collection(search_condition, COLLECTION_NAME) + + # verify + self.dict_tester.assertEqualKeys(expected_dict, dict_) + self.dict_tester.assertEqualValues(expected_dict, dict_) + + def test_search_array_nested_doc_with_array_in_collection_returns_one_dict(self): + search_condition = {'tests': {'$elemMatch': {'test_name': 'test2'}}} + expected_dict = {'id_test': '1', 'tests': [{'test_name': 'search_doc_in_collection'}, + {'test_name': 'test2'}]} + + # insert document + self.test_db_collection.insert_one(copy(expected_dict)) + + # search document + dict_ = self.db.search_document_in_collection(search_condition, COLLECTION_NAME) + + # verify + self.dict_tester.assertEqualKeys(expected_dict, dict_) + self.dict_tester.assertEqualValues(expected_dict, dict_) + + def test_search_doc_in_collection_returns_none(self): + search_condition = {'test_name': 'not_found'} + expected_dict = {'id_test': '1', 'test_name': 'search_doc_in_collection'} + + # insert document + self.test_db_collection.insert_one(expected_dict) + + # search document + dict_ = self.db.search_document_in_collection(search_condition, COLLECTION_NAME) + + # verify + self.assertIsNone(dict_) + + def test_get_documents_from_collection(self): + self.test_db_collection.insert_many(documents=[{'id_doc': 1}, {'id_doc': 2}, {'id_doc': 3}, {'id_doc': 4}]) + + # get documents + docs = self.db.get_documents_from_collection(COLLECTION_NAME) + + # verify + self.assertEqual(4, len(docs)) + self.assertEqual({'id_doc': 1}, docs[0]) + self.assertEqual({'id_doc': 2}, docs[1]) + self.assertEqual({'id_doc': 3}, docs[2]) + self.assertEqual({'id_doc': 4}, docs[3]) + + def test_delete_document_from_collection(self): + # insert document to be deleted + self.test_db_collection.insert_one({'id_doc': 1, 'text': 'this is a test'}) + + # delete document + self.db.delete_document_from_collection({'id_doc': 1}, COLLECTION_NAME) + + # verify + self.assertIsNone(self.test_db_collection.find_one({'id_doc': 1})) + + def test_get_documents_from_collection_in_range_0_3(self): + self.test_db_collection.insert_many(documents=DOCUMENTS) + documents = self.db.get_documents_from_collection_in_range(COLLECTION_NAME, skip=0, limit=3) + self.assertEqual(3, len(documents)) + self.assertEqual(DOCUMENTS[0].get('id'), documents[0].get('id')) + self.assertEqual(DOCUMENTS[1].get('id'), documents[1].get('id')) + self.assertEqual(DOCUMENTS[2].get('id'), documents[2].get('id')) + + def test_get_documents_from_collection_in_range_5_0(self): + self.test_db_collection.insert_many(documents=DOCUMENTS) + documents = self.db.get_documents_from_collection_in_range(COLLECTION_NAME, skip=5, limit=0) + self.assertEqual(len(DOCUMENTS)-5, len(documents)) + self.assertEqual(DOCUMENTS[5].get('id'), documents[0].get('id')) + self.assertEqual(DOCUMENTS[len(DOCUMENTS)-1].get('id'), documents[len(documents)-1].get('id')) + + def test_get_documents_from_collection_in_range_2_1(self): + self.test_db_collection.insert_many(documents=DOCUMENTS) + documents = self.db.get_documents_from_collection_in_range(COLLECTION_NAME, skip=2, limit=1) + self.assertEqual(1, len(documents)) + self.assertEqual(3, documents[0].get('id')) + + def test_get_documents_from_collection_in_range_0_0_returns_all_documents(self): + self.test_db_collection.insert_many(documents=DOCUMENTS) + documents = self.db.get_documents_from_collection_in_range(COLLECTION_NAME, skip=0, limit=0) + self.assertEqual(len(DOCUMENTS), len(documents)) + + def test_aggregation(self): + self.test_db_collection.insert_many(documents=[CVE_1, CVE_2, CVE_3]) + # search condition is met in one document + search_condition = {'cpe_entries': {'$elemMatch': {'$and': [{'wfn.product': 'adobe_reader'}, {'wfn.vendor': 'adobe'}]}}} + aggregation_filter_ = {'input': '$cpe_entries', 'as': 'cpe_entries', + 'cond': {'$and': [{'$eq': ['$$cpe_entries.wfn.product', 'adobe_reader']}, + {'$eq': ['$$cpe_entries.wfn.vendor', 'adobe']}]}} + aggregation = {'cpe_entries': {'$filter': aggregation_filter_}, '_id': 0, 'cve_id': 1, 'cve_summary': 1} + documents = self.db.search_documents_and_aggregate(search_condition, aggregation, COLLECTION_NAME) + self.assertEqual(1, len(documents)) + cpe_entries = documents[0].get('cpe_entries') + self.assertEqual(1, len(cpe_entries)) + wfn = cpe_entries[0].get('wfn') + self.assertEqual('adobe_reader', wfn.get('product')) + self.assertEqual('adobe', wfn.get('vendor')) + + # search condition is met in two documents + search_condition = {'$and': [{'cpe_entries.wfn.product': 'reader'}, {'cpe_entries.wfn.vendor': 'adobe'}]} + aggregation_filter_ = {'input': '$cpe_entries', 'as': 'cpe_entries', + 'cond': {'$and': [{'$eq': ['$$cpe_entries.wfn.product', 'reader']}, + {'$eq': ['$$cpe_entries.wfn.vendor', 'adobe']}]}} + aggregation = {'cpe_entries': {'$filter': aggregation_filter_}, '_id': 0, 'cve_id': 1, 'cve_summary': 1} + documents = self.db.search_documents_and_aggregate(search_condition, aggregation, COLLECTION_NAME) + self.assertEqual(2, len(documents)) + + # search condition is not met + search_condition = {'$and': [{'cpe_entries.wfn.product': 'reader__'}, {'cpe_entries.wfn.vendor': '__adobe'}]} + aggregation_filter_ = {'input': '$cpe_entries', 'as': 'cpe_entries', + 'cond': {'$and': [{'$eq': ['$$cpe_entries.wfn.product', 'reader']}, + {'$eq': ['$$cpe_entries.wfn.vendor', 'adobe']}]}} + aggregation = {'cpe_entries': {'$filter': aggregation_filter_}, '_id': 0, 'cve_id': 1, 'cve_summary': 1} + documents = self.db.search_documents_and_aggregate(search_condition, aggregation, COLLECTION_NAME) + self.assertEqual(0, len(documents)) + + + def tearDown(self): + self.db.close() + self.mongodb_client.drop_database(IVA_DB_NAME) + self.mongodb_client.close() + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_frontend/__init__.py b/tests/test_frontend/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_frontend/test_add_cpe_inventory_handler.py b/tests/test_frontend/test_add_cpe_inventory_handler.py new file mode 100644 index 0000000..f089f69 --- /dev/null +++ b/tests/test_frontend/test_add_cpe_inventory_handler.py @@ -0,0 +1,46 @@ +import unittest +from unittest.mock import patch, MagicMock, Mock +from inventory import software_cpe +from django import template +from database import Database + +template.loader = Mock(return_value='TemplateLoader') + +GLPI_ITEM = {'id': 'dtr8ewg4af5dz9uj', 'os': '', 'vendor': 'microsoft_corporation', + 'product': 'microsoft_.net_framework_4_client_profile_deu_language_pack', 'version': '4.0.30319'} +WFN_POST = {'part': ['a'], 'vendor': ['microsoft'], 'product': ['.net_framework'], 'version': ['4.0.30319'], + 'update': ['ANY'], 'edition': ['ANY'], 'language': ['ANY'], 'sw_edition': ['ANY'], 'target_sw': ['ANY'], + 'target_hw': ['ANY'], 'other': ['ANY']} +WFN = {'part': 'a', 'vendor': 'microsoft', 'product': '.net_framework', 'version': '4.0.30319', 'update': 'ANY', + 'edition': 'ANY', 'language': 'ANY', 'sw_edition': 'ANY', 'target_sw': 'ANY', 'target_hw': 'ANY', 'other': 'ANY'} +URI = 'cpe:/a:microsoft:.net_framework:4.0.30319' + + +class TestCaseAddCPEHandler(unittest.TestCase): + + def test_handle_post_request(self): + cpe_inventory_mock = MagicMock(spec=software_cpe.SoftwareCPE) + db_mock = MagicMock(spec=Database) + with patch('django.http.HttpResponse', return_value=None): + with patch('inventory.inventory.Database', return_value=db_mock): + with patch('inventory.software_cpe.SoftwareCPE', return_value=cpe_inventory_mock): + from frontend.iva.handlers.assign_cpe_handler import handle_request + handle_request(MockPOSTRequest()) + cpe_inventory_mock.create_sw_cpe_dict.assert_called_with(WFN) + + +class MockPOSTRequest: + + def __init__(self): + self.method = 'POST' + self.POST = create_post_dict() + + +def create_post_dict(): + dict_ = {'inventory_item': str(GLPI_ITEM), 'csrfmiddlewaretoken': ['G2Tg4v6Lo7pZ1bnIrz5b6X37jysLMTYH']} + dict_.update(WFN_POST) + return dict_ + + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/tests/test_frontend/test_change_status_handler.py b/tests/test_frontend/test_change_status_handler.py new file mode 100644 index 0000000..8917ea8 --- /dev/null +++ b/tests/test_frontend/test_change_status_handler.py @@ -0,0 +1,47 @@ +import unittest +from unittest.mock import MagicMock, patch, call + +from alerts import alerts +from inventory import software_cve_matches + +SOFTWARE_ID = 'd5f8sfd8sa4di90o' + + +class TestCaseChangeAlertStatusHandler(unittest.TestCase): + + def test_change_status_from_new_to_removed(self): + # 1 Alerts: change status from new to remove + # 2 CVE-Matches: set CVEs as negative + alerts_mock = MagicMock(spec=alerts.Alerts) + alerts_mock.get_software_alert.return_value = {'cves': ['cve1', 'cve2']} + cve_matches_mock = MagicMock(spec=software_cve_matches.CVEMatches) + with patch('django.http.HttpResponse', return_value=None): + with patch('alerts.alerts.Alerts', return_value=alerts_mock): + with patch('inventory.software_cve_matches.CVEMatches', return_value=cve_matches_mock): + from frontend.iva.handlers.alert_status_handler import handle_request + handle_request(MockPOSTRequest('removed')) + self.assertEqual(call.get_software_alert(SOFTWARE_ID), alerts_mock.method_calls[0]) + self.assertEqual(call.change_alert_status(SOFTWARE_ID, 'removed'), alerts_mock.method_calls[1]) + cve_matches_mock.set_cve_match_as_negative.assert_any_call(SOFTWARE_ID, 'cve1') + cve_matches_mock.set_cve_match_as_negative.assert_any_call(SOFTWARE_ID, 'cve2') + + # only when status is set to removed CVEs are set as negative + handle_request(MockPOSTRequest('any_status')) + self.assertEqual(2, len(cve_matches_mock.method_calls)) + + handle_request(MockPOSTRequest('removed')) + self.assertEqual(4, len(cve_matches_mock.method_calls)) + + +class MockPOSTRequest: + + def __init__(self, new_status): + self.method = 'POST' + self.POST = create_post_dict(new_status) + + +def create_post_dict(new_status): + return {'software_id': SOFTWARE_ID, 'new_status': new_status} + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_frontend/test_request_handler_utils.py b/tests/test_frontend/test_request_handler_utils.py new file mode 100644 index 0000000..b9685a8 --- /dev/null +++ b/tests/test_frontend/test_request_handler_utils.py @@ -0,0 +1,54 @@ +import unittest +from tests.dict_tester import DictTester +from frontend.iva.handlers import request_handler_utils + + +class TestRequestHandlerUtils(unittest.TestCase): + + def setUp(self): + self.dict_tester = DictTester() + + def test_is_post_request_returns_true(self): + self.assertTrue(request_handler_utils.is_post_request(MockRequest('POST'))) + + def test_is_post_request_returns_false(self): + self.assertFalse(request_handler_utils.is_post_request(MockRequest('GET'))) + + def test_is_get_request_returns_true(self): + self.assertTrue(request_handler_utils.is_get_request(MockRequest('GET'))) + + def test_is_get_request_returns_false(self): + self.assertFalse(request_handler_utils.is_get_request(MockRequest('POST'))) + + def test_is_post_request_returns_false_when_no_method(self): + self.assertFalse(request_handler_utils.is_post_request(MockRequest(''))) + + def test_is_get_request_returns_false_when_no_method(self): + self.assertFalse(request_handler_utils.is_get_request(MockRequest(''))) + + def test_create_dict_from_string(self): + expected_dict = {'key': 'value1', 'key2': {'key3': 'value3'}} + + dict_string = '{"key": "value1", "key2": {"key3": "value3"}}' + self.dict_tester.assertEqualKeys(expected_dict, request_handler_utils.create_dict_from_string(dict_string)) + self.dict_tester.assertEqualValues(expected_dict, request_handler_utils.create_dict_from_string(dict_string)) + + dict_string = "{'key': 'value1', 'key2': {'key3': 'value3'}}" + self.dict_tester.assertEqualKeys(expected_dict, request_handler_utils.create_dict_from_string(dict_string)) + self.dict_tester.assertEqualValues(expected_dict, request_handler_utils.create_dict_from_string(dict_string)) + + def test_create_dict_from_string_returns_false(self): + self.assertFalse(request_handler_utils.create_dict_from_string('')) + self.assertFalse(request_handler_utils.create_dict_from_string(None)) + self.assertFalse(request_handler_utils.create_dict_from_string('[a, b, c]')) + self.assertFalse(request_handler_utils.create_dict_from_string('{"a":}')) + + +class MockRequest: + + def __init__(self, method): + self.method = method + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_inventory/__init__.py b/tests/test_inventory/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_inventory/test_inventory.py b/tests/test_inventory/test_inventory.py new file mode 100644 index 0000000..eb4001a --- /dev/null +++ b/tests/test_inventory/test_inventory.py @@ -0,0 +1,289 @@ +import hashlib +import unittest +from copy import copy +from tests.mock_config import * +from pymongo import MongoClient +from tests.dict_tester import DictTester +from inventory.inventory import Inventory, INVENTORY_DB_COLLECTION, generate_software_id + +SOFTWARE_ID = 'dtr8ewg4af5dz9uj' +NEW_SOFTWARE_1 = {'version': '0.6.15-2ubuntu9.7', 'product': 'accountsservice', 'vendor': 'Ubuntu'} +NEW_SOFTWARE_2 = {'version': '0.6.35-0ubuntu7.1', 'product': 'accountsservice', 'vendor': None} +NEW_SOFTWARE_3 = {'version': '8.0.7601.17514', 'product': 'Internet Explorer', 'vendor': 'Microsoft Corporation'} +NEW_SOFTWARE_4 = {'version': '4.5.51209', 'product': 'Microsoft .NET Framework 4.5.2', 'vendor': 'Microsoft Corporation'} +NEW_SOFTWARE_5 = {'version': '9.0.21022', 'product': 'Microsoft Visual C++ 2008 Redistributable - x86 9.0.21022', + 'vendor': 'Microsoft Corporation'} +GLPI_DB_ITEMS = [NEW_SOFTWARE_1, NEW_SOFTWARE_2, NEW_SOFTWARE_3, NEW_SOFTWARE_4, NEW_SOFTWARE_5] + + +class TestInventory(unittest.TestCase): + def setUp(self): + self.mongodb_client = MongoClient(DB_HOST, DB_PORT) + self.test_db = self.mongodb_client[IVA_DB_NAME] + self.inventory_collection = self.test_db[INVENTORY_DB_COLLECTION] + self.dict_tester = DictTester() + self.create_inventory_object() + + def create_inventory_object(self): + self.inventory = patch_config_for('inventory.inventory', 'Inventory') + + def test_insert_new_software(self): + new_software = {'version': '0.6.15-2ubuntu9.7', 'product': 'accountsservice', 'vendor': 'Ubuntu'} + self.inventory.insert_software_in_db(new_software) + + # verify if item inserted + software_id = self.generate_expected_software_id(new_software) + expected = {'id': software_id, 'version': '0.6.15-2ubuntu9.7', 'product': 'accountsservice', 'vendor': 'Ubuntu', + 'cpe': None, 'cve_matches': []} + new_inserted_software = self.inventory_collection.find_one({'id': software_id}, {'_id': 0}) + self.assertIsNotNone(new_inserted_software) + self.dict_tester.assertEqualKeys(expected, new_inserted_software) + self.dict_tester.assertEqualValues(expected, new_inserted_software) + + def test_insert_new_software_to_db_does_not_insert_duplicates(self): + new_software = {'version': '0.6.15-2ubuntu9.7', 'product': 'accountsservice', 'vendor': 'Ubuntu'} + self.inventory.insert_software_in_db(copy(new_software)) + self.inventory.insert_software_in_db(copy(new_software)) + + # verify that only 1 item was inserted + self.assertEqual(1, self.inventory_collection.count()) + + def test_generate_software_id(self): + self.assertEqual(self.generate_expected_software_id(NEW_SOFTWARE_3), generate_software_id(NEW_SOFTWARE_3)) + self.assertEqual(self.generate_expected_software_id(NEW_SOFTWARE_2), generate_software_id(NEW_SOFTWARE_2)) + + def test_get_inventory(self): + # mock insert inventory in db + software_1 = {'id': '1', 'version': '0.6.15-2ubuntu9.7', 'product': 'accountsservice', 'vendor': 'Ubuntu', + 'cpe': None, 'cve_matches': []} + software_2 = {'id': '2', 'version': '0.6.15-2ubuntu9.7', 'product': 'accountsservice', 'vendor': 'Ubuntu', + 'cpe': None, 'cve_matches': [{'cve_id': 'CVE-2005-0509', 'positive': 0, 'removed': 0}]} + software_3 = {'id': '3', 'version': '0.6.15-2ubuntu9.7', 'product': 'accountsservice', 'vendor': 'Ubuntu', + 'cpe': {'uri_binding': '', 'wfn': {}}, 'cve_matches': []} + self.inventory_collection.insert_many(documents=[software_1, software_2, software_3]) + + # get inventory + inventory = self.inventory.get_inventory() + + # verify + self.assertEqual(3, len(inventory)) + + def test_get_software_products_with_assigned_cpe_returns_one_product(self): + # mock insert inventory in db + software_1 = {'id': '1', 'version': '0.6.15-2ubuntu9.7', 'product': 'accountsservice', 'vendor': 'Ubuntu', + 'cpe': None, 'cve_matches': []} + software_2 = {'id': '2', 'version': '0.6.15-2ubuntu9.7', 'product': 'accountsservice', 'vendor': 'Ubuntu', + 'cpe': None, 'cve_matches': [{'cve_id': 'CVE-2005-0509', 'positive': 0, 'removed': 0}]} + software_3 = {'id': '3', 'version': '0.6.15-2ubuntu9.7', 'product': 'accountsservice', 'vendor': 'Ubuntu', + 'cpe': {'uri_binding': '', 'wfn': {}}, 'cve_matches': []} + self.inventory_collection.insert_many(documents=[software_1, software_2, software_3]) + + # get items + software_with_cpe = self.inventory.get_software_products_with_assigned_cpe() + + # verify + self.assertEqual(1, len(software_with_cpe)) + + def test_get_software_products_with_assigned_cpe_returns_empty_list(self): + # mock insert inventory in db + software_1 = {'id': '1', 'version': '0.6.15-2ubuntu9.7', 'product': 'accountsservice', 'vendor': 'Ubuntu', + 'cpe': None, 'cve_matches': []} + software_2 = {'id': '2', 'version': '0.6.15-2ubuntu9.7', 'product': 'accountsservice', 'vendor': 'Ubuntu', + 'cpe': None, 'cve_matches': [{'cve_id': 'CVE-2005-0509', 'positive': 0, 'removed': 0}]} + software_3 = {'id': '3', 'version': '0.6.15-2ubuntu9.7', 'product': 'accountsservice', 'vendor': 'Ubuntu', + 'cpe': None, 'cve_matches': []} + self.inventory_collection.insert_many(documents=[software_1, software_2, software_3]) + + # get items + software_with_cpe = self.inventory.get_software_products_with_assigned_cpe() + + # verify + self.assertEqual(0, len(software_with_cpe)) + + def test_get_software_products_without_assigned_cpe_returns_two_products(self): + # mock insert inventory in db + software_1 = {'id': '1', 'version': '0.6.15-2ubuntu9.7', 'product': 'accountsservice', 'vendor': 'Ubuntu', + 'cpe': None, 'cve_matches': []} + software_2 = {'id': '2', 'version': '0.6.15-2ubuntu9.7', 'product': 'accountsservice', 'vendor': 'Ubuntu', + 'cpe': None, 'cve_matches': [{'cve_id': 'CVE-2005-0509', 'positive': 0, 'removed': 0}]} + software_3 = {'id': '3', 'version': '0.6.15-2ubuntu9.7', 'product': 'accountsservice', 'vendor': 'Ubuntu', + 'cpe': {'uri_binding': '', 'wfn': {}}, 'cve_matches': []} + self.inventory_collection.insert_many(documents=[software_1, software_2, software_3]) + + # get items + software_without_cpe = self.inventory.get_software_products_without_assigned_cpe() + + # verify + self.assertEqual(2, len(software_without_cpe)) + + def test_get_software_products_without_assigned_cpe_returns_three_products(self): + # mock insert inventory in db + software_1 = {'id': '1', 'version': '0.6.15-2ubuntu9.7', 'product': 'accountsservice', 'vendor': 'Ubuntu', + 'cpe': None, 'cve_matches': []} + software_2 = {'id': '2', 'version': '0.6.15-2ubuntu9.7', 'product': 'accountsservice', 'vendor': 'Ubuntu', + 'cpe': None, 'cve_matches': []} + software_3 = {'id': '3', 'version': '0.6.15-2ubuntu9.7', 'product': 'accountsservice', 'vendor': 'Ubuntu', + 'cpe': None, 'cve_matches': []} + self.inventory_collection.insert_many(documents=[software_1, software_2, software_3]) + + # get items + software_without_cpe = self.inventory.get_software_products_without_assigned_cpe() + + # verify + self.assertEqual(3, len(software_without_cpe)) + + def test_get_software_without_assigned_cpe_order_by_vendor(self): + # mock insert items and assign cpe + item = copy(NEW_SOFTWARE_1) + item.update({'id': self.generate_expected_software_id(item)}) + self.inventory_collection.insert_one(item) + + item = copy(NEW_SOFTWARE_2) + item.update({'id': self.generate_expected_software_id(item)}) + self.inventory_collection.insert_one(item) + + item = copy(NEW_SOFTWARE_3) + item.update({'id': self.generate_expected_software_id(item)}) + self.inventory_collection.insert_one(item) + + item = copy(NEW_SOFTWARE_4) + item.update({'id': self.generate_expected_software_id(item)}) + self.inventory_collection.insert_one(item) + + item = copy(NEW_SOFTWARE_5) + item.update({'id': self.generate_expected_software_id(item)}) + self.inventory_collection.insert_one(item) + + items = self.inventory.get_software_products_without_assigned_cpe() + + # verify + self.assertTrue('Microsoft' in str(items[0].get('vendor'))) + self.assertTrue('Microsoft' in str(items[1].get('vendor'))) + self.assertTrue('Microsoft' in str(items[2].get('vendor'))) + self.assertTrue('Ubuntu' in str(items[3].get('vendor'))) + self.assertTrue(items[4].get('vendor') is None) + + def test_search_software_without_assigned_cpe_by_product_regex(self): + software_1 = {'product': 'Microsoft Visual C++ 2008 Redistributable - x86 9.0.21022'} + software_2 = {'product': 'Internet Explorer'} + software_3 = {'product': 'Microsoft Visual C++ 2013 Redistributable (x86) - 12.0.40649'} + software_4 = {'product': 'Microsoft .NET Framework 4.6'} + software_5 = {'product': 'Mozilla Firefox 48.0.2 (x86 en-GB)'} + self.inventory_collection.insert_many(documents=[copy(software_1), copy(software_2), copy(software_3), + copy(software_4), copy(software_5)]) + sw_products = self.inventory.search_software_products_without_assigned_cpe('visuAL') + self.assertEqual(2, len(sw_products)) + self.assertEqual(software_1, sw_products[0]) + self.assertEqual(software_3, sw_products[1]) + + sw_products = self.inventory.search_software_products_without_assigned_cpe('') + self.assertEqual(5, len(sw_products)) + + def test_insert_new_software_products_to_db(self): + item = copy(NEW_SOFTWARE_1) + item.update({'id': self.generate_expected_software_id(item)}) + self.inventory_collection.insert_one(item) + + item = copy(NEW_SOFTWARE_2) + item.update({'id': self.generate_expected_software_id(item)}) + self.inventory_collection.insert_one(item) + + item = copy(NEW_SOFTWARE_3) + item.update({'id': self.generate_expected_software_id(item)}) + self.inventory_collection.insert_one(item) + + with patch('config.get_database_host', return_value=DB_HOST): + with patch('config.get_database_port', return_value=DB_PORT): + with patch('config.get_database_name', return_value=IVA_DB_NAME): + with patch('config.is_database_authentication_enabled', return_value=False): + with patch('inventory.glpi_inventory.read_inventory', return_value=GLPI_DB_ITEMS): + self.assertEqual(3, self.inventory_collection.count()) + Inventory().insert_new_software_products_to_db() + self.assertEqual(5, self.inventory_collection.count()) + + def test_get_software_product_from_inventory(self): + software_id = 'a1f1h4j56' + self.inventory_collection.insert_one( + {'id': software_id, 'version': '0.6.15-2ubuntu9.7', 'product': 'accountsservice', + 'vendor': 'Ubuntu', 'cpe': None, 'cve_matches': []}) + + software = self.inventory.get_software_by_id(software_id) + + self.assertIsNotNone(software) + self.assertEqual(software_id, software.get('id')) + + def test_get_vendors_returns_three_vendors(self): + self.inventory_collection.insert_many( + documents=[{'cpe': {'wfn': {'version': '0.6.15-2', 'product': 'accountsservice', 'vendor': 'ubuntu'}}}, + {'cpe': {'wfn': {'version': '8', 'product': 'internet_explorer', 'vendor': 'microsoft'}}}, + {'cpe': {'wfn': {'version': '1.3', 'product': 'iTunes', 'vendor': 'apple'}}}]) + + vendors = self.inventory.get_vendors() + self.assertEqual(3, len(vendors)) + self.assertTrue('ubuntu' in vendors) + self.assertTrue('microsoft' in vendors) + self.assertTrue('apple' in vendors) + + def test_get_vendors_returns_one_vendor(self): + self.inventory_collection.insert_many( + documents=[{'cpe': {'wfn': {'version': '9', 'product': 'internet_explorer', 'vendor': 'microsoft'}}}, + {'cpe': {'wfn': {'version': '8', 'product': 'internet_explorer', 'vendor': 'microsoft'}}}, + {'cpe': {'wfn': {'version': '7', 'product': 'internet_explorer', 'vendor': 'microsoft'}}}]) + vendors = self.inventory.get_vendors() + self.assertEqual(1, len(vendors)) + self.assertTrue('microsoft' in vendors) + + def test_get_vendors_returns_empty_list(self): + self.assertEqual(0, len(self.inventory.get_vendors())) + + def test_get_products_returns_three_products(self): + self.inventory_collection.insert_many( + documents=[{'cpe': {'wfn': {'version': '7', 'product': 'internet_explorer', 'vendor': 'microsoft'}}}, + {'cpe': {'wfn': {'version': '8', 'product': 'office', 'vendor': 'microsoft'}}}, + {'cpe': {'wfn': {'version': '2007', 'product': 'visual_c++', 'vendor': 'microsoft'}}}]) + products = self.inventory.get_products() + self.assertEqual(3, len(products)) + self.assertTrue('internet_explorer' in products) + self.assertTrue('office' in products) + self.assertTrue('visual_c++' in products) + + def test_get_products_returns_one_product(self): + self.inventory_collection.insert_many( + documents=[{'cpe': {'wfn': {'version': '7', 'product': 'internet_explorer', 'vendor': 'microsoft'}}}, + {'cpe': {'wfn': {'version': '10', 'product': 'internet_explorer', 'vendor': 'microsoft'}}}]) + products = self.inventory.get_products() + self.assertEqual(1, len(products)) + self.assertTrue('internet_explorer' in products) + + def test_get_vendor_products(self): + self.inventory_collection.insert_many( + documents=[{'cpe': {'wfn': {'version': '0.6.15-2', 'product': 'accountsservice', 'vendor': 'ubuntu'}}}, + {'cpe': {'wfn': {'version': '9', 'product': 'internet_explorer', 'vendor': 'microsoft'}}}, + {'cpe': {'wfn': {'version': '8', 'product': 'office', 'vendor': 'microsoft'}}}, + {'cpe': {'wfn': {'version': '7', 'product': 'office', 'vendor': 'microsoft'}}}, + {'cpe': {'wfn': {'version': '1.3', 'product': 'iTunes', 'vendor': 'apple'}}}]) + + products = self.inventory.get_vendor_products('microsoft') + self.assertEqual(2, len(products)) + self.assertTrue('internet_explorer' in products) + self.assertTrue('office' in products) + + products = self.inventory.get_vendor_products('ubuntu') + self.assertEqual(1, len(products)) + self.assertTrue('accountsservice' in products) + + products = self.inventory.get_vendor_products('apple') + self.assertEqual(1, len(products)) + self.assertTrue('iTunes' in products) + + def generate_expected_software_id(self, new_software): + seed = str(new_software.get('product')) + str(new_software.get('vendor')) + str(new_software.get('version')) + return hashlib.md5(str.encode(seed)).hexdigest() + + def tearDown(self): + self.mongodb_client.drop_database(IVA_DB_NAME) + self.mongodb_client.close() + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_inventory/test_software_cpe.py b/tests/test_inventory/test_software_cpe.py new file mode 100644 index 0000000..57dcd39 --- /dev/null +++ b/tests/test_inventory/test_software_cpe.py @@ -0,0 +1,136 @@ +import unittest +from copy import copy +from alerts.alerts import ALERTS_DB_COLLECTION +from inventory.inventory import INVENTORY_DB_COLLECTION +from tests.dict_tester import DictTester +from pymongo import MongoClient +from tests.mock_config import * + +SOFTWARE_ID = 'bdzjhf65g48j1w786454jnm8' +URI_BINDING = 'cpe:/a:1024cms:1024_cms:0.7' +NEW_URI_BINDING = 'cpe:/a:1025cms:1025_cms:0.7' +WFN = {'part': 'a', 'vendor': '1024cms', 'product': '1024_cms', 'version': '0.7', 'update': 'ANY', 'edition': 'ANY', + 'sw_edition': 'ANY', 'target_sw': 'ANY', 'target_hw': 'ANY', 'other': 'ANY', 'language': 'ANY'} +MODIFIED_WFN = {'part': 'a', 'vendor': '1025cms', 'product': '1025_cms', 'version': '0.7', 'update': 'ANY', + 'edition': 'ANY', 'sw_edition': 'ANY', 'target_sw': 'ANY', 'target_hw': 'ANY', 'other': 'ANY', + 'language': 'ANY'} +CPE_DOC = {'uri_binding': URI_BINDING, 'wfn': WFN} +CVE_MATCHES = [{'cve_id': 'CVE-2016-1256'}, {'cve_id': 'CVE-2016-1257'}] + + +class TestCPEInventory(unittest.TestCase): + + def setUp(self): + self.mongodb_client = MongoClient(DB_HOST, DB_PORT) + self.test_db = self.mongodb_client[IVA_DB_NAME] + self.inventory_collection = self.test_db[INVENTORY_DB_COLLECTION] + self.alert_collection = self.test_db[ALERTS_DB_COLLECTION] + self.dict_tester = DictTester() + self.create_software_cpe_obj() + + def create_software_cpe_obj(self): + self.cpe_inventory = patch_config_for('inventory.software_cpe', 'SoftwareCPE') + + def test_create_cpe_inventory_document(self): + # call method being tested + document = self.cpe_inventory.create_sw_cpe_dict(WFN) + + # verify + self.dict_tester.assertEqualKeys(CPE_DOC, document) + self.dict_tester.assertEqualValues(CPE_DOC, document) + + def test_assign_cpe_to_software(self): + # insert software + self.inventory_collection.insert_one({'id': SOFTWARE_ID, 'version': '', 'product': '', 'vendor': '', + 'cpe': None, + 'cve_matches': []}) + + # assign cpe to software + cpe = {'uri_binding': URI_BINDING, 'wfn': WFN} + self.cpe_inventory.assign_cpe_to_software(cpe, SOFTWARE_ID) + + # verify + software = self.inventory_collection.find_one({'id': SOFTWARE_ID}) + self.assertEqual(cpe, software.get('cpe')) + + def test_update_software_cpe(self): + # insert software which CPE will be updated + self.inventory_collection.insert_one({'id': SOFTWARE_ID, 'version': '', 'product': '', 'vendor': '', + 'cpe': CPE_DOC, + 'cve_matches': []}) + + # update CPE + self.cpe_inventory.update_software_cpe(SOFTWARE_ID, MODIFIED_WFN) + + # verify update + software = self.inventory_collection.find_one({'id': SOFTWARE_ID}) + updated_cpe = software.get('cpe') + self.assertIsNotNone(updated_cpe) + self.assertEqual(NEW_URI_BINDING, updated_cpe.get('uri_binding')) + self.dict_tester.assertEqualKeys(MODIFIED_WFN, updated_cpe.get('wfn')) + self.dict_tester.assertEqualValues(MODIFIED_WFN, updated_cpe.get('wfn')) + + def test_update_software_cpe_removes_cve_matches(self): + # insert software + self.inventory_collection.insert_one({'id': SOFTWARE_ID, 'version': '', 'product': '', 'vendor': '', + 'cpe': CPE_DOC, + 'cve_matches': CVE_MATCHES}) + + # update CPE + self.cpe_inventory.update_software_cpe(SOFTWARE_ID, MODIFIED_WFN) + + # verify cve_matches were removed + software = self.inventory_collection.find_one({'id': SOFTWARE_ID}) + self.assertEqual(0, len(software.get('cve_matches'))) + + def test_update_software_cpe_changes_alert_status_to_removed(self): + # insert software + self.inventory_collection.insert_one({'id': SOFTWARE_ID, 'version': '', 'product': '', 'vendor': '', + 'cpe': CPE_DOC, + 'cve_matches': CVE_MATCHES}) + + # insert alert + self.alert_collection.insert_one({'generated_on': '', 'software_id': SOFTWARE_ID, + 'cves': ['CVE-2016-1256', 'CVE-2016-1257'], + 'status': 'new', 'log': [''], 'notes': ''}) + + # update CPE + self.cpe_inventory.update_software_cpe(SOFTWARE_ID, MODIFIED_WFN) + + # verify alert status was changed to removed + alert = self.alert_collection.find_one({'software_id': SOFTWARE_ID}) + self.assertEqual('removed', alert.get('status')) + + def test_get_software_cpe_by_id(self): + # insert software + self.inventory_collection.insert_one({'id': SOFTWARE_ID, 'version': '', 'product': '', 'vendor': '', + 'cpe': CPE_DOC, + 'cve_matches': []}) + self.assertEqual(CPE_DOC, self.cpe_inventory.get_software_cpe_by_id(SOFTWARE_ID)) + + def test_get_software_cpe_by_id_returns_none(self): + self.assertIsNone(self.cpe_inventory.get_software_cpe_by_id('dsd')) + + def test_get_software_cpe_by_uri_binding(self): + # insert software + self.inventory_collection.insert_one({'id': SOFTWARE_ID, 'version': '', 'product': '', 'vendor': '', + 'cpe': CPE_DOC, + 'cve_matches': []}) + self.assertEqual(CPE_DOC, self.cpe_inventory.get_software_cpe_by_uri(URI_BINDING)) + + def test_get_software_cpe_by_uri_binding_returns_none(self): + self.assertIsNone(self.cpe_inventory.get_software_cpe_by_uri('dsdsdf')) + + def insert_cpe_item(self): + self.inventory_collection.insert_one(copy(CPE_DOC)) + + def insert_alert(self): + self.alert_collection.insert_one({'generated_on': '', 'software_id': SOFTWARE_ID, + 'cves': ['cve1', 'cve2', 'cve3'], 'status': 'new', 'log': [''], 'notes': ''}) + + def tearDown(self): + self.mongodb_client.drop_database(IVA_DB_NAME) + self.mongodb_client.close() + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_inventory/test_software_cve_matches.py b/tests/test_inventory/test_software_cve_matches.py new file mode 100644 index 0000000..86c0801 --- /dev/null +++ b/tests/test_inventory/test_software_cve_matches.py @@ -0,0 +1,596 @@ +import unittest +from pymongo import MongoClient +from alerts.alerts import ALERTS_DB_COLLECTION +from inventory.inventory import INVENTORY_DB_COLLECTION +from inventory.software_cve_matches import sort_cve_matches_by_version +from tests.dict_tester import DictTester +from tests.mock_config import * + +SOFTWARE_ID = '134jk429' + + +class TestCVEMatches(unittest.TestCase): + + def setUp(self): + self.mongodb_client = MongoClient(DB_HOST, DB_PORT) + self.test_db = self.mongodb_client[IVA_DB_NAME] + self.inventory_collection = self.test_db[INVENTORY_DB_COLLECTION] + self.alerts_collection = self.test_db[ALERTS_DB_COLLECTION] + self.dict_tester = DictTester() + self.create_cve_matches_obj() + + def create_cve_matches_obj(self): + self.cve_matches = patch_config_for('inventory.software_cve_matches', 'CVEMatches') + + def test_insert_matches(self): + # insert software + self.inventory_collection.insert_one({'id': SOFTWARE_ID, 'cpe': {'wfn': {'version': '1.2'}}, 'cve_matches': []}) + + # insert cve_matches for inventory item + matches_found_for_software = [{'cve_id': 'CVE-2005-0509', + 'cve_summary': 'Multiple cross-site scripting (XSS) vulnerabilities', + 'cpe_entries': [{'uri_binding': 'cpe:/a:microsoft:.net_framework:1.0:sp2', + 'wfn': {'language': 'ANY', 'version': '1.0', + 'product': '.net_framework', 'edition': 'ANY', + 'vendor': 'microsoft', 'other': 'ANY', 'update': 'sp2', + 'part': 'a', 'sw_edition': 'ANY', 'target_sw': 'ANY', + 'target_hw': 'ANY'}}, + {'uri_binding': 'cpe:/a:microsoft:.net_framework:1.0', + 'wfn': {'language': 'ANY', 'version': '1.0', 'product': '.net_framework', + 'edition': 'ANY', + 'vendor': 'microsoft', 'other': 'ANY', 'update': 'ANY', + 'part': 'a', 'sw_edition': 'ANY', 'target_sw': 'ANY', + 'target_hw': 'ANY'}}]}, + {'cve_id': 'CVE-2005-2127', + 'cve_summary': 'Microsoft Internet Explorer 5.01, 5.5, and 6 allows ."', + 'cpe_entries': [{'uri_binding': 'cpe:/a:microsoft:.net_framework:1.1', + 'wfn': {'language': 'ANY', 'version': '1.1', + 'product': '.net_framework', 'edition': 'ANY', + 'vendor': 'microsoft', 'other': 'ANY', + 'update': 'ANY', 'part': 'a', 'sw_edition': 'ANY', + 'target_sw': 'ANY', 'target_hw': 'ANY'}}]}] + self.cve_matches.insert_software_cve_matches(SOFTWARE_ID, matches_found_for_software) + + # verify that cve_matches for inventory item were inserted + inserted_matches = self.inventory_collection.find_one({'id': SOFTWARE_ID}).get('cve_matches') + self.assertIsNotNone(inserted_matches) + # expected document must have two additional fields per match: positive and remove. Moreover, summary and + # cpe_entries fields are deleted from each match + expected_matches = [{'cve_id': 'CVE-2005-0509', + 'cpe_entries': ['cpe:/a:microsoft:.net_framework:1.0:sp2', + 'cpe:/a:microsoft:.net_framework:1.0'], + 'positive': 0, 'removed': 0}, + {'cve_id': 'CVE-2005-2127', 'cpe_entries': ['cpe:/a:microsoft:.net_framework:1.1'], + 'positive': 0, 'removed': 0}] + self.assertListEqual(expected_matches, inserted_matches) + + def test_insert_matches_updates_matches_and_updates_alert_matches(self): + # insert software with two matches + cve_1 = {'cve_id': 'CVE-2005-0501', 'cpe_entries': [], 'positive': 1, 'removed': 0} + cve_2 = {'cve_id': 'CVE-2005-0502', 'cpe_entries': [], 'positive': 0, 'removed': 0} + cve_3 = {'cve_id': 'CVE-2005-0503', 'cpe_entries': [], 'positive': 1, 'removed': 0} + self.inventory_collection.insert_one({'id': SOFTWARE_ID, + 'cve_matches': [cve_1, cve_2, cve_3], + 'cpe': {'uri_binding': 'cpe:/a:microsoft:.net_framework:1.1', + 'wfn': {'version': '1.2'}}}) + + # insert alert, since CVE-2005-0502 is positive + self.alerts_collection.insert_one({'generated_on': '', + 'software_id': SOFTWARE_ID, + 'cves': ['CVE-2005-0501', 'CVE-2005-0503'], 'status': 'new', + 'log': [''], 'notes': ''}) + # insert new CVE matches + # CVE already exist + new_found_cve_1 = {'cve_id': 'CVE-2005-0501', + 'cve_summary': 'Multiple cross-site scripting (XSS) vulnerabilities', + 'cpe_entries': [{'uri_binding': 'cpe:/a:microsoft:.net_framework:1.0:sp2', 'wfn': ''}, + {'uri_binding': 'cpe:/a:microsoft:.net_framework:1.0', 'wfn': ''}]} + # New CVE + new_found_cve_4 = {'cve_id': 'CVE-2005-0504', + 'cve_summary': 'Multiple cross-site scripting (XSS) vulnerabilities', + 'cpe_entries': [{'uri_binding': 'cpe:/a:microsoft:.net_framework:1.0:sp2', 'wfn': ''}, + {'uri_binding': 'cpe:/a:microsoft:.net_framework:1.0', 'wfn': ''}]} + cve_4 = {'cve_id': 'CVE-2005-0504', 'positive': 0, 'removed': 0, + 'cpe_entries': ['cpe:/a:microsoft:.net_framework:1.0:sp2', 'cpe:/a:microsoft:.net_framework:1.0']} + + self.cve_matches.insert_software_cve_matches(SOFTWARE_ID, [new_found_cve_1, new_found_cve_4]) + + # verify that cve_matches for inventory item were updated + updated_matches = self.inventory_collection.find_one({'id': SOFTWARE_ID}).get('cve_matches') + self.assertEqual(2, len(updated_matches)) + self.assertTrue(cve_1 in updated_matches) + self.assertTrue(cve_4 in updated_matches) + + # verify alert was updated + alert_cves = self.alerts_collection.find_one({'software_id': SOFTWARE_ID}).get('cves') + self.assertEqual(1, len(alert_cves)) + self.assertFalse('CVE-2005-0503' in alert_cves) + self.assertTrue('CVE-2005-0501' in alert_cves) + + def test_add_new_match(self): + # insert software + software_matches = [{'cve_id': 'CVE-2005-0509', 'cpe_entries': ['cpe:/a:microsoft:.net_framework:3.5'], + 'positive': 0, 'removed': 1}] + self.inventory_collection.insert_one({'id': SOFTWARE_ID, 'cpe': {'wfn': {'version': '1.2'}}, + 'cve_matches': software_matches}) + + # add new match to software + new_match = {'cve_id': 'CVE-2016-0149', 'cve_summary': 'Microsoft .NET Framework 2.0, 3.0 SP2, 3.5, 3.5.1,"', + 'cpe_entries': [{'uri_binding': 'cpe:/a:microsoft:.net_framework:3.5.1', + 'wfn': {'language': 'ANY', 'version': '3.5.1', 'product': '.net_framework', + 'edition': 'ANY', 'vendor': 'microsoft', 'other': 'ANY', 'update': 'ANY', + 'part': 'a', 'sw_edition': 'ANY', 'target_sw': 'ANY', 'target_hw': 'ANY'}}]} + self.cve_matches.add_new_cve_match_to_software(SOFTWARE_ID, new_match) + + # verify + expected_matches = [{'cve_id': 'CVE-2005-0509', 'positive': 0, 'removed': 1, + 'cpe_entries': ['cpe:/a:microsoft:.net_framework:3.5']}, + {'cve_id': 'CVE-2016-0149', 'positive': 0, 'removed': 0, + 'cpe_entries': ['cpe:/a:microsoft:.net_framework:3.5.1']}] + updated_matches = self.inventory_collection.find_one({'id': SOFTWARE_ID}).get('cve_matches') + self.assertListEqual(expected_matches, updated_matches) + + def test_set_match_as_removed(self): + # insert document to be updated + cve_id = 'CVE-2005-0509' + software = {'id': SOFTWARE_ID, + 'cve_matches': [{'cve_id': cve_id, 'positive': 0, 'removed': 0, 'cpe_entries': []}], + 'cpe': {'wfn': {'version': '1.2'}}} + self.inventory_collection.insert_one(software) + + self.cve_matches.set_cve_match_as_removed(SOFTWARE_ID, cve_id) + + # self.verify_cve_match_status('removed', 1) + match = self.get_software_cve_match(SOFTWARE_ID, cve_id) + self.assertEqual(1, match.get('removed')) + + def test_restore_cve_match(self): + # insert document to be updated + cve_id = 'CVE-2005-0509' + software = {'id': SOFTWARE_ID, 'cpe': {'wfn': {'version': '1.2'}}, + 'cve_matches': [{'cve_id': cve_id, 'positive': 0, 'removed': 1, 'cpe_entries': []}]} + self.inventory_collection.insert_one(software) + + self.cve_matches.restore_cve_match(SOFTWARE_ID, cve_id) + + match = self.get_software_cve_match(SOFTWARE_ID, cve_id) + self.assertEqual(0, match.get('removed')) + + def test_set_match_as_positive(self): + # insert document to be updated + cve_id = 'CVE-2005-0509' + software = {'id': SOFTWARE_ID, 'cpe': {'wfn': {'version': '1.2'}}, + 'cve_matches': [{'cve_id': cve_id, 'positive': 0, 'removed': 0, 'cpe_entries': []}]} + self.inventory_collection.insert_one(software) + + # set cve match as positive + self.cve_matches.set_cve_match_as_positive(SOFTWARE_ID, cve_id) + + # verify status was changed to positive + match = self.get_software_cve_match(SOFTWARE_ID, cve_id) + self.assertEqual(1, match.get('positive')) + + # verify new alert was generated + self.assertIsNotNone(self.alerts_collection.find_one({'software_id': SOFTWARE_ID})) + + def test_set_match_as_positive_does_not_create_new_alert_when_already_alert_exists(self): + cve_id = 'CVE-2005-0509' + software = {'id': SOFTWARE_ID, + 'cpe': {'wfn': {'version': '1.2'}}, + 'cve_matches': [{'cve_id': cve_id, 'positive': 0, 'removed': 0, 'cpe_entries': []}]} + self.inventory_collection.insert_one(software) + + self.assertEqual(0, len(list(self.alerts_collection.find({'software_id': SOFTWARE_ID})))) + # when the first CVE is set as positive an alert is created. For the next ones, the created alert is updated + self.cve_matches.set_cve_match_as_positive(SOFTWARE_ID, cve_id) + self.cve_matches.set_cve_match_as_positive(SOFTWARE_ID, cve_id) + self.assertEqual(1, len(list(self.alerts_collection.find({'software_id': SOFTWARE_ID})))) + + def test_set_match_as_negative(self): + # insert document to be updated + cve_id = 'CVE-2005-0509' + software = {'id': SOFTWARE_ID, 'cpe': {'wfn': {'version': '1.2'}}, + 'cve_matches': [{'cve_id': cve_id, 'positive': 1, 'removed': 1, 'cpe_entries': []}], } + self.inventory_collection.insert_one(software) + + self.alerts_collection.insert_one({'generated_on': '', + 'software_id': SOFTWARE_ID, + 'cves': [cve_id, 'CVE-2005-0510'], 'status': 'new', + 'log': [''], 'notes': ''}) + + # set cve as negative + self.cve_matches.set_cve_match_as_negative(SOFTWARE_ID, cve_id) + + # verify status changed to negative + self.assertEqual(0, self.get_software_cve_match(SOFTWARE_ID, cve_id).get('positive')) + + # verify CVE-2005-0509 was removed from item alert + alert_cves = self.alerts_collection.find_one({'software_id': SOFTWARE_ID}).get('cves') + self.assertTrue(cve_id not in alert_cves) + + def test_exist_cve_matches_for_software_returns_true(self): + self.inventory_collection.insert_one({'id': SOFTWARE_ID, 'cpe': {'wfn': {'version': '1.2'}}, + 'cve_matches': [{'cve_id': 'CVE-2005-0509', 'positive': 1, 'removed': 1, + 'cpe_entries': []}]}) + self.assertTrue(self.cve_matches.exist_cve_matches_for_software(SOFTWARE_ID)) + + def test_exist_cve_matches_for_inventory_item_return_false(self): + self.inventory_collection.insert_one({'id': SOFTWARE_ID, 'cve_matches': [], 'cpe': {'wfn': {'version': '1.2'}}}) + self.assertFalse(self.cve_matches.exist_cve_matches_for_software(SOFTWARE_ID)) + + def test_get_software_cve_matches_returns_one_match(self): + # insert software with one CVE match + self.inventory_collection.insert_one({'id': SOFTWARE_ID, 'cpe': {'wfn': {'version': '1.2'}}, + 'cve_matches': [{'cve_id': 'CVE-2005-0509', 'positive': 1, 'removed': 1, + 'cpe_entries': []}]}) + # verify + self.assertEqual(1, len(self.cve_matches.get_software_cve_matches(SOFTWARE_ID))) + + def test_get_all_vulnerable_software_returns_two_items(self): + vulnerable_software_1 = {'id': '1478', 'cpe': {'wfn': {'version': '1.2'}}, + 'cve_matches': [{'cve_id': 'CVE-2005-0509', 'removed': 0, 'positive': 1, + 'cve_summary': '', 'cpe_entries': []}, + {'cve_id': 'CVE-2005-0510', 'removed': 1, 'positive': 0, + 'cve_summary': '', 'cpe_entries': []}]} + + not_vulnerable_software = {'id': '68665', 'cpe': {'wfn': {'version': '1.2'}}, + 'cve_matches': [{'cve_id': 'CVE-2005-0509', 'removed': 0, 'positive': 0, + 'cve_summary': '', 'cpe_entries': []}, + {'cve_id': 'CVE-2005-0510', 'removed': 0, 'positive': 0, + 'cve_summary': '', 'cpe_entries': []}]} + + vulnerable_software_2 = {'id': '45469', 'cpe': {'wfn': {'version': '1.2'}}, + 'cve_matches': [{'cve_id': 'CVE-2005-0509', 'removed': 1, 'positive': 0, + 'cve_summary': '', 'cpe_entries': []}, + {'cve_id': 'CVE-2005-0510', 'removed': 0, 'positive': 1, + 'cve_summary': '', 'cpe_entries': []}]} + + self.inventory_collection.insert_many(documents=[vulnerable_software_1, vulnerable_software_2, not_vulnerable_software]) + + # verify + vulnerable_software_items = self.cve_matches.get_vulnerable_software_items() + self.assertEqual(2, len(vulnerable_software_items)) + self.assertEqual('1478', vulnerable_software_items[0].get('id')) + self.assertEqual('45469', vulnerable_software_items[1].get('id')) + + def test_get_software_cve_matches_for_returns_three_matches(self): + # insert software with 3 CVE matches + software = {'id': SOFTWARE_ID, 'cpe': {'wfn': {'version': '1.2'}}, + 'cve_matches': [{'cve_id': 'CVE-2005-0509', 'removed': 0, 'positive': 0, + 'cve_summary': '', 'cpe_entries': []}, + {'cve_id': 'CVE-2005-0510', 'removed': 0, 'positive': 0, + 'cve_summary': '', 'cpe_entries': []}, + {'cve_id': 'CVE-2005-0510', 'removed': 1, 'positive': 0, + 'cve_summary': '', 'cpe_entries': []}]} + + self.inventory_collection.insert_one(software) + + # verify + self.assertEqual(3, len(self.cve_matches.get_software_cve_matches(SOFTWARE_ID))) + + def test_get_software_cve_matches_returns_two_matches(self): + # insert software with 2 matches + software = {'id': SOFTWARE_ID, 'cpe': {'wfn': {'version': '1.2'}}, + 'cve_matches': [{'cve_id': 'CVE-2005-0509', 'removed': 1, 'positive': 0, + 'cve_summary': '', 'cpe_entries': []}, + {'cve_id': 'CVE-2005-0510', 'removed': 1, 'positive': 0, + 'cve_summary': '', 'cpe_entries': []}]} + self.inventory_collection.insert_one(software) + + # verify + self.assertEqual(2, len(self.cve_matches.get_software_cve_matches(SOFTWARE_ID))) + + def test_remove_software_cve_matches(self): + # insert software with CVE matches to be deleted + software = {'id': SOFTWARE_ID, 'cpe': {'wfn': {'version': '1.2'}}, + 'cve_matches': [{'cve_id': 'CVE-2005-0509', 'removed': 1, 'positive': 0, + 'cve_summary': '', 'cpe_entries': []}, + {'cve_id': 'CVE-2005-0510', 'removed': 1, 'positive': 0, + 'cve_summary': '', 'cpe_entries': []}]} + self.inventory_collection.insert_one(software) + + # remove software CVE matches + self.cve_matches.remove_software_cve_matches(SOFTWARE_ID) + + # verify + software = self.inventory_collection.find_one({'id': SOFTWARE_ID}) + self.assertEqual(0, len(software.get('cve_matches'))) + + def test_get_cve_matches_returns_one_match(self): + # insert software items + software_1 = {'id': SOFTWARE_ID, 'version': '0.6.15-2ubuntu9.7', 'product': 'accountsservice', + 'vendor': 'Ubuntu', 'cpe': {'wfn': {'version': '1.2'}}, + 'cve_matches': [{'cve_id': 'CVE-2005-0509', 'removed': 0, 'positive': 0, 'cpe_entries': []}]} + software_2 = {'id': '123', 'cpe': {'wfn': {'version': '1.2'}}, 'cve_matches': []} + software_3 = {'id': '124', 'cpe': {'wfn': {'version': '1.2'}}, 'cve_matches': []} + self.inventory_collection.insert_many(documents=[software_1, software_2, software_3]) + + # get software with CVE matches + cve_matches = self.cve_matches.get_cve_matches() + + # verify + expected_match = {'cve_id': 'CVE-2005-0509', 'removed': 0, 'positive': 0, 'cpe_entries': [], + 'software': {'id': SOFTWARE_ID, 'version': '0.6.15-2ubuntu9.7', 'product': 'accountsservice', + 'vendor': 'Ubuntu', 'cpe': {'wfn': {'version': '1.2'}}}} + self.assertEqual(1, len(cve_matches)) + self.dict_tester.assertEqualKeys(expected_match, cve_matches[0]) + self.dict_tester.assertEqualValues(expected_match, cve_matches[0]) + + def test_get_cve_matches_returns_empty_list(self): + self.assertEqual(0, len(self.cve_matches.get_cve_matches())) + + def test_get_software_items_with_cve_matches_returns_three_matches(self): + # insert software items + software_1 = {'id': SOFTWARE_ID, 'cpe': {'wfn': {'version': '1.2'}}, + 'cve_matches': [{'cve_id': 'CVE-2005-0509', 'removed': 0, 'positive': 0, 'cve_summary': '', + 'cpe_entries': []}]} + software_2 = {'id': '123', 'cpe': {'wfn': {'version': '1.2'}}, + 'cve_matches': [{'cve_id': 'CVE-2005-0510', 'removed': 0, 'positive': 0, 'cve_summary': '', + 'cpe_entries': []}, {'cve_id': 'CVE-2005-0511', 'removed': 0, 'positive': 0, + 'cve_summary': '', 'cpe_entries': []}]} + software_3 = {'id': '124', 'cpe': {'wfn': {'version': '1.2'}}, 'cve_matches': []} + self.inventory_collection.insert_many(documents=[software_1, software_2, software_3]) + + # get software with CVE matches + cve_matches = self.cve_matches.get_cve_matches() + + # verify + self.assertEqual(3, len(cve_matches)) + + def test_get_cve_matches_without_removed_matches(self): + # insert software items + software_1 = {'id': SOFTWARE_ID, 'cpe': {'wfn': {'version': '1.2'}}, + 'cve_matches': [{'cve_id': 'CVE-2005-0509', 'removed': 1, 'positive': 0, 'cve_summary': '', + 'cpe_entries': []}, {'cve_id': 'CVE-2005-0510', 'removed': 1, 'positive': 0, + 'cve_summary': '', 'cpe_entries': []}]} + software_2 = {'id': '123', 'cve_matches': [], 'cpe': {'wfn': {'version': '1.2'}}} + software_3 = {'id': '124', 'cve_matches': [{'cve_id': 'CVE-2005-0511', 'removed': 0, 'positive': 0, + 'cve_summary': '', 'cpe_entries': []}], 'cpe': {'wfn': {'version': '1.2'}}} + self.inventory_collection.insert_many(documents=[software_1, software_2, software_3]) + + # get software with CVE matches + software_with_cve_matches = self.cve_matches.get_cve_matches(['hide_removed']) + + # verify + self.assertEqual(1, len(software_with_cve_matches)) + self.assertEqual('CVE-2005-0511', software_with_cve_matches[0].get('cve_id')) + self.assertEqual('124', software_with_cve_matches[0].get('software').get('id')) + + def test_get_cve_matches_ordered_by_cve_year_descending(self): + # insert software items + software_1 = {'id': SOFTWARE_ID, 'cve_matches': [{'cve_id': 'CVE-2005-0510', 'removed': 0, 'positive': 0, + 'cpe_entries': []}, + {'cve_id': 'CVE-2005-0519', 'removed': 1, 'positive': 0, + 'cpe_entries': []}]} + software_2 = {'id': '123', 'cve_matches': [{'cve_id': 'CVE-2005-0501', 'removed': 1, 'positive': 0, + 'cpe_entries': []}, + {'cve_id': 'CVE-2016-9999', 'removed': 1, 'positive': 0, + 'cpe_entries': []}]} + + self.inventory_collection.insert_many(documents=[software_1, software_2]) + + # get software with CVE matches + cve_matches = self.cve_matches.get_cve_matches(['ordered_by_year_desc']) + + # verify + self.assertEqual(cve_matches[0].get('cve_id'), 'CVE-2016-9999') + self.assertEqual(cve_matches[1].get('cve_id'), 'CVE-2005-0519') + self.assertEqual(cve_matches[2].get('cve_id'), 'CVE-2005-0510') + self.assertEqual(cve_matches[3].get('cve_id'), 'CVE-2005-0501') + + def test_get_cve_matches_ordered_by_cve_year_ascending(self): + # insert software items + software_1 = {'id': SOFTWARE_ID, 'cve_matches': [{'cve_id': 'CVE-2005-0510', 'removed': 0, 'positive': 0, + 'cpe_entries': []}, + {'cve_id': 'CVE-2005-0519', 'removed': 1, 'positive': 0, + 'cpe_entries': []}]} + software_2 = {'id': '123', 'cve_matches': [{'cve_id': 'CVE-2004-0501', 'removed': 1, 'positive': 0, + 'cpe_entries': []}, + {'cve_id': 'CVE-2016-9999', 'removed': 1, 'positive': 0, + 'cpe_entries': []}]} + + self.inventory_collection.insert_many(documents=[software_1, software_2]) + + # get software with CVE matches + cve_matches = self.cve_matches.get_cve_matches(['ordered_by_year_asc']) + + # verify + self.assertEqual(cve_matches[0].get('cve_id'), 'CVE-2004-0501') + self.assertEqual(cve_matches[1].get('cve_id'), 'CVE-2005-0510') + self.assertEqual(cve_matches[2].get('cve_id'), 'CVE-2005-0519') + self.assertEqual(cve_matches[3].get('cve_id'), 'CVE-2016-9999') + + def test_get_cve_matches_without_cve_matches_ordered_by_cve(self): + # insert software items + software_1 = {'id': SOFTWARE_ID, 'cve_matches': [{'cve_id': 'CVE-2005-0510', 'removed': 0, 'positive': 0, + 'cpe_entries': []}, + {'cve_id': 'CVE-2005-0501', 'removed': 1, 'positive': 0, + 'cpe_entries': []}]} + software_2 = {'id': '123', 'cve_matches': [{'cve_id': 'CVE-2005-0519', 'removed': 1, 'positive': 0, + 'cpe_entries': []}]} + software_3 = {'id': '124', 'cve_matches': [{'cve_id': 'CVE-2006-9999', 'removed': 0, 'positive': 0, + 'cpe_entries': []}]} + + self.inventory_collection.insert_many(documents=[software_1, software_2, software_3]) + + # get software with CVE matches + cve_matches = self.cve_matches.get_cve_matches(['ordered_by_year_desc', 'hide_removed']) + + # verify + self.assertEqual(2, len(cve_matches)) + self.assertEqual(cve_matches[0].get('cve_id'), 'CVE-2006-9999') + self.assertEqual(cve_matches[1].get('cve_id'), 'CVE-2005-0510') + self.assertEqual(cve_matches[0].get('software').get('id'), '124') + self.assertEqual(cve_matches[1].get('software').get('id'), SOFTWARE_ID) + + def test_get_vendor_product_cves_matches(self): + # insert software items + matches_1 = [{'cve_id': 'CVE-2005-0509'}, {'cve_id': 'CVE-2005-2127'}] + matches_2 = [{'cve_id': 'CVE-2005-0510'}, {'cve_id': 'CVE-2005-2128'}] + matches_3 = [{'cve_id': 'CVE-2005-0511'}, {'cve_id': 'CVE-2005-2129'}] + software_1 = {'cpe': {'wfn': {'vendor': 'microsoft', 'product': 'office'}}, 'cve_matches': matches_1} + software_2 = {'cpe': {'wfn': {'vendor': 'microsoft', 'product': 'internet_explorer'}}, 'cve_matches': matches_2} + software_3 = {'cpe': {'wfn': {'vendor': 'ubuntu', 'product': 'openssl'}}, 'cve_matches': matches_3} + + self.inventory_collection.insert_many(documents=[software_1, software_2, software_3]) + + cve_matches = self.cve_matches.get_vendor_product_cve_matches('all') + self.assertEqual(6, len(cve_matches)) + + cve_matches = self.cve_matches.get_vendor_product_cve_matches('microsoft') + self.assertEqual(4, len(cve_matches)) + + cve_matches = self.cve_matches.get_vendor_product_cve_matches('ubuntu') + self.assertEqual(2, len(cve_matches)) + + cve_matches = self.cve_matches.get_vendor_product_cve_matches('microsoft', 'office') + self.assertEqual(2, len(cve_matches)) + + cve_matches = self.cve_matches.get_vendor_product_cve_matches('microsoft', 'internet_explorer') + self.assertEqual(2, len(cve_matches)) + + cve_matches = self.cve_matches.get_vendor_product_cve_matches('microsoft', 'visual_studio') + self.assertEqual(0, len(cve_matches)) + + cve_matches = self.cve_matches.get_vendor_product_cve_matches('apple', 'itunes') + self.assertEqual(0, len(cve_matches)) + + def test_get_software_cve_matches_with_same_cpe_entries_as_cve(self): + cve_id = 'CVE-2005-0510' + cpe_entries = ['cpe:/a:microsoft:.net_framework:1.0:sp2'] + software = {'id': SOFTWARE_ID, 'cve_matches': [{'cve_id': cve_id, 'removed': 0, 'positive': 0, + 'cpe_entries': cpe_entries}, + {'cve_id': 'CVE-2005-0501', 'removed': 0, 'positive': 0, + 'cpe_entries': cpe_entries}, + {'cve_id': 'CVE-2002-0089', 'removed': 0, 'positive': 0, + 'cpe_entries': ['cpe:/a:microsoft:.net_framework:1.0:sp2', + 'cpe:/a:microsoft:.net_framework:1.0:sp1']}, + {'cve_id': 'CVE-2016-0001', 'removed': 0, 'positive': 0, + 'cpe_entries': ['cpe:/a:microsoft:.net_framework:4.0:sp2']}], + 'cpe': {'wfn': {'version': '1.8'}}} + self.inventory_collection.insert_one(software) + cve_matches = self.cve_matches.get_software_cve_matches_with_same_cpe_entries_as_cve(SOFTWARE_ID, cve_id) + self.assertEqual(2, len(cve_matches)) + self.assertEqual(cve_id, cve_matches[0].get('cve_id')) + self.assertEqual('CVE-2005-0501', cve_matches[1].get('cve_id')) + + def test_get_software_cve_matches_with_same_cpe_entries_removed_and_positive_values_as_cve(self): + cve_id = 'CVE-2005-0510' + cpe_entries = ['cpe:/a:microsoft:.net_framework:1.0:sp2'] + software = {'id': SOFTWARE_ID, 'cve_matches': [{'cve_id': cve_id, 'removed': 0, 'positive': 0, + 'cpe_entries': cpe_entries}, + {'cve_id': 'CVE-2005-0501', 'removed': 0, 'positive': 1, + 'cpe_entries': cpe_entries}, + {'cve_id': 'CVE-2006-9928', 'removed': 1, 'positive': 0, + 'cpe_entries': cpe_entries}], 'cpe': {'wfn': {'version': '1.2'}}} + self.inventory_collection.insert_one(software) + cve_matches = self.cve_matches.get_software_cve_matches_with_same_cpe_entries_as_cve(SOFTWARE_ID, cve_id) + self.assertEqual(1, len(cve_matches)) + self.assertEqual(cve_id, cve_matches[0].get('cve_id')) + + def test_set_matches_group_as_positive(self): + cve_id_master = 'CVE-2005-0510' + cve_id_1 = 'CVE-2005-0501' + cve_id_2 = 'CVE-2006-9928' + cpe_entries = ['cpe:/a:microsoft:.net_framework:1.0:sp2'] + software = {'id': SOFTWARE_ID, 'cve_matches': [{'cve_id': cve_id_master, 'removed': 0, 'positive': 0, + 'cpe_entries': cpe_entries}, + {'cve_id': cve_id_1, 'removed': 0, 'positive': 0, + 'cpe_entries': cpe_entries}, + {'cve_id': cve_id_2, 'removed': 1, 'positive': 0, + 'cpe_entries': cpe_entries}], 'cpe': {'wfn': {'version': '1.2'}}} + self.inventory_collection.insert_one(software) + self.cve_matches.set_cve_matches_group_as_positive(SOFTWARE_ID, cve_id_master) + self.assertEqual(1, self.get_software_cve_match(SOFTWARE_ID, cve_id_master).get('positive')) + self.assertEqual(1, self.get_software_cve_match(SOFTWARE_ID, cve_id_1).get('positive')) + self.assertEqual(0, self.get_software_cve_match(SOFTWARE_ID, cve_id_2).get('positive')) + + def test_set_matches_group_as_negative(self): + cve_id_master = 'CVE-2005-0510' + cve_id_1 = 'CVE-2005-0501' + cve_id_2 = 'CVE-2006-9928' + cpe_entries = ['cpe:/a:microsoft:.net_framework:1.0:sp2'] + software = {'id': SOFTWARE_ID, 'cve_matches': [{'cve_id': cve_id_master, 'removed': 0, 'positive': 1, + 'cpe_entries': cpe_entries}, + {'cve_id': cve_id_1, 'removed': 0, 'positive': 1, + 'cpe_entries': cpe_entries}, + {'cve_id': cve_id_2, 'removed': 1, 'positive': 0, + 'cpe_entries': cpe_entries}], 'cpe': {'wfn': {'version': '1.2'}}} + self.alerts_collection.insert_one({'generated_on': '', 'software_id': SOFTWARE_ID, + 'cves': [cve_id_master, cve_id_1, cve_id_2], + 'status': 'new', 'log': [''], 'notes': ''}) + self.inventory_collection.insert_one(software) + self.cve_matches.set_cve_matches_group_as_negative(SOFTWARE_ID, cve_id_master) + self.assertEqual(0, self.get_software_cve_match(SOFTWARE_ID, cve_id_master).get('positive')) + self.assertEqual(0, self.get_software_cve_match(SOFTWARE_ID, cve_id_1).get('positive')) + self.assertEqual(0, self.get_software_cve_match(SOFTWARE_ID, cve_id_2).get('positive')) + + def test_set_cve_matches_group_as_removed(self): + cve_id_master = 'CVE-2005-0510' + cve_id_1 = 'CVE-2005-0501' + cve_id_2 = 'CVE-2006-9928' + cpe_entries = ['cpe:/a:microsoft:.net_framework:1.0:sp2'] + software = {'id': SOFTWARE_ID, 'cve_matches': [{'cve_id': cve_id_master, 'removed': 0, 'positive': 0, + 'cpe_entries': cpe_entries}, + {'cve_id': cve_id_1, 'removed': 0, 'positive': 0, + 'cpe_entries': cpe_entries}, + {'cve_id': cve_id_2, 'removed': 0, 'positive': 1, + 'cpe_entries': cpe_entries}], + 'cpe': {'wfn': {'version': '1.2'}}} + self.alerts_collection.insert_one({'generated_on': '', 'software_id': SOFTWARE_ID, + 'cves': [cve_id_master, cve_id_1, cve_id_2], + 'status': 'new', 'log': [''], 'notes': ''}) + self.inventory_collection.insert_one(software) + self.cve_matches.set_cve_matches_group_as_removed(SOFTWARE_ID, cve_id_master) + self.assertEqual(1, self.get_software_cve_match(SOFTWARE_ID, cve_id_master).get('removed')) + self.assertEqual(1, self.get_software_cve_match(SOFTWARE_ID, cve_id_1).get('removed')) + self.assertEqual(0, self.get_software_cve_match(SOFTWARE_ID, cve_id_2).get('removed')) + + def test_restore_cve_matches_group(self): + cve_id_master = 'CVE-2005-0510' + cve_id_1 = 'CVE-2005-0501' + cve_id_2 = 'CVE-2006-9928' + cpe_entries = ['cpe:/a:microsoft:.net_framework:1.0:sp2'] + software = {'id': SOFTWARE_ID, 'cve_matches': [{'cve_id': cve_id_master, 'removed': 1, 'positive': 0, + 'cpe_entries': cpe_entries}, + {'cve_id': cve_id_1, 'removed': 1, 'positive': 0, + 'cpe_entries': cpe_entries}, + {'cve_id': cve_id_2, 'removed': 0, 'positive': 0, + 'cpe_entries': cpe_entries}],'cpe': {'wfn': {'version': '1.2'}}} + self.alerts_collection.insert_one({'generated_on': '', 'software_id': SOFTWARE_ID, + 'cves': [cve_id_master, cve_id_1, cve_id_2], + 'status': 'new', 'log': [''], 'notes': ''}) + self.inventory_collection.insert_one(software) + self.cve_matches.restore_cve_matches_group(SOFTWARE_ID, cve_id_master) + self.assertEqual(0, self.get_software_cve_match(SOFTWARE_ID, cve_id_master).get('removed')) + self.assertEqual(0, self.get_software_cve_match(SOFTWARE_ID, cve_id_1).get('removed')) + self.assertEqual(0, self.get_software_cve_match(SOFTWARE_ID, cve_id_2).get('removed')) + + def test_cve_matches_are_prioritized_by_equal_version(self): + software = {'id': SOFTWARE_ID, 'cve_matches': [], 'cpe': {'wfn': {'version': '1.5.6'}}} + matches = [{'cve_id': 'CVE-2015-0001', 'removed': 1, 'positive': 0, + 'cpe_entries': ['cpe:/a:microsoft:.net_framework:1.0:sp2', + 'cpe:/a:microsoft:.net_framework:1.2.0:sp2']}, + {'cve_id': 'CVE-2015-0002', 'removed': 1, 'positive': 0, + 'cpe_entries': ['cpe:/a:microsoft:.net_framework:1.6', + 'cpe:/a:microsoft:.net_framework:1.9:sp2']}, + {'cve_id': 'CVE-2015-0003', 'removed': 0, 'positive': 0, + 'cpe_entries': ['cpe:/a:microsoft:.net_framework:1.0:sp2', + 'cpe:/a:microsoft:.net_framework:1.5.6:sp2']}] + self.inventory_collection.insert_one(software) + sorted_matches = sort_cve_matches_by_version(matches, '1.5.6') + self.assertEqual('CVE-2015-0003', sorted_matches[0].get('cve_id')) + + def get_software_cve_match(self, software_id, cve_id): + matches = self.inventory_collection.find_one({'id': software_id}).get('cve_matches') + for match in matches: + if match.get('cve_id') == cve_id: + return match + + def tearDown(self): + self.mongodb_client.drop_database(IVA_DB_NAME) + self.mongodb_client.close() + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_local_repositories/__init__.py b/tests/test_local_repositories/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_local_repositories/test_cpe_dict.py b/tests/test_local_repositories/test_cpe_dict.py new file mode 100644 index 0000000..2d6b27c --- /dev/null +++ b/tests/test_local_repositories/test_cpe_dict.py @@ -0,0 +1,89 @@ +import unittest +from pymongo import MongoClient +from tests.mock_config import * +from local_repositories.cpe_dict import IVA_CPE_COLLECTION +from local_repositories.cve_search import CVE_SEARCH_CPE_COLLECTION + + +cve_search_cpe1 = {"id": "cpe:2.3:a:%240.99_kindle_books_project:%240.99_kindle_books:6:-:-:-:-:android", + "references": ["https://play.google.com/store/apps/details?id=com.kindle.books.for99"], + "cpe_2_2": "cpe:/a:%240.99_kindle_books_project:%240.99_kindle_books:6::~~~android~~", + "title": "$0.99 Kindle Books project $0.99 Kindle Books (aka com.kindle.books.for99) for android 6.0"} +cve_search_cpe2 = {"id": "cpe:2.3:a:1024cms:1024_cms:0.7", "cpe_2_2": "cpe:/a:1024cms:1024_cms:0.7", + "title": "1024cms.org 1024 CMS 0.7"} +cve_search_cpe3 = {"id": "cpe:2.3:a:1024cms:1024_cms:1.2.5", "cpe_2_2": "cpe:/a:1024cms:1024_cms:1.2.5", + "title": "1024cms.org 1024 CMS 1.2.5"} + +iva_cpe1 = {"wfn": {"sw_edition": "ANY", "target_hw": "ANY", "version": "6", "target_sw": "android", + "vendor": "$0.99_kindle_books_project", "product": "$0.99_kindle_books", "edition": "ANY", + "language": "ANY", "part": "a", "update": "ANY", "other": "ANY"}, + "formatted_string_binding": "cpe:2.3:a:%240.99_kindle_books_project:%240.99_kindle_books:6:-:-:-:-:android", + "uri_binding": "cpe:/a:%240.99_kindle_books_project:%240.99_kindle_books:6::~~~android~~"} +iva_cpe2 = {"wfn": {"sw_edition": "ANY", "target_hw": "ANY", "version": "0.7", "target_sw": "ANY", "vendor": "1024cms", + "product": "1024_cms", "edition": "ANY", "language": "ANY", "part": "a", "update": "ANY", "other": "ANY"}, + "formatted_string_binding": "cpe:2.3:a:1024cms:1024_cms:0.7", + "uri_binding": "cpe:/a:1024cms:1024_cms:0.7"} +iva_cpe3 = {"wfn": {"sw_edition": "ANY", "target_hw": "ANY", "version": "1.2.5", "target_sw": "ANY", + "vendor": "1024cms", "product": "1024_cms", "edition": "ANY", "language": "ANY", "part": "a", + "update": "ANY", "other": "ANY"}, + "formatted_string_binding": "cpe:2.3:a:1024cms:1024_cms:1.2.5", + "uri_binding": "cpe:/a:1024cms:1024_cms:1.2.5"} + + +class TestCPEDict(unittest.TestCase): + + def setUp(self): + self.create_mongodb_client() + self.create_collections() + self.fill_search_cve_cpe_collection() + self.create_cpe_db_object() + + def create_cpe_db_object(self): + self.cpe_db = patch_config_for('local_repositories.cpe_dict', 'CPEDict') + + def test_update_iva_cpes_collection(self): + self.cpe_db.update_cpe_dict() + self.verify_update() + + def test_update_iva_cpes_collection_when_collection_already_updated(self): + self.cpe_db.update_cpe_dict() + self.cpe_db.update_cpe_dict() + self.verify_update() + + def verify_update(self): + iva_cpes = self.get_iva_cpes() + self.assertTrue(iva_cpe1 in iva_cpes) + self.assertTrue(iva_cpe2 in iva_cpes) + self.assertTrue(iva_cpe3 in iva_cpes) + self.assertEqual(3, self.iva_cpe_collection.count()) + + def get_iva_cpes(self): + cpes = list(self.iva_cpe_collection.find()) + for cpe in cpes: + cpe.pop('_id') + return cpes + + def create_mongodb_client(self): + self.mongodb_client = MongoClient(DB_HOST, DB_PORT) + + def create_collections(self): + self.iva_db = self.mongodb_client[IVA_DB_NAME] + self.cve_search_db = self.mongodb_client[CVE_SEARCH_DB_NAME] + self.cve_search_cpe_collection = self.cve_search_db[CVE_SEARCH_CPE_COLLECTION] + self.iva_cpe_collection = self.iva_db[IVA_CPE_COLLECTION] + + def fill_search_cve_cpe_collection(self): + bulk = self.cve_search_cpe_collection.initialize_ordered_bulk_op() + bulk.insert(cve_search_cpe1) + bulk.insert(cve_search_cpe2) + bulk.insert(cve_search_cpe3) + bulk.execute() + + def tearDown(self): + self.mongodb_client.drop_database(IVA_DB_NAME) + self.mongodb_client.drop_database(CVE_SEARCH_DB_NAME) + self.mongodb_client.close() + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_local_repositories/test_cve_feeds.py b/tests/test_local_repositories/test_cve_feeds.py new file mode 100644 index 0000000..bae9562 --- /dev/null +++ b/tests/test_local_repositories/test_cve_feeds.py @@ -0,0 +1,151 @@ +import unittest +from copy import copy +from pymongo import MongoClient +from tests.mock_config import * +from local_repositories.cve_search import CVE_SEARCH_CVE_COLLECTION +from local_repositories.cve_feeds import IVA_CVE_COLLECTION, BATCH_SIZE +from tests.dict_tester import DictTester + +cve_search_cve1 = {"cvss": 5, "Modified": "2008-09-09T08:33:31.007Z", "access": {"complexity": "LOW", "vector": "NETWORK", "authentication": "NONE"}, + "references": ["http://www.microsoft.com/technet/security/bulletin/ms98-008.asp"], "Published" : "1997-12-16T00:00:00Z", + "impact": {"availability": "PARTIAL", "integrity": "NONE", "confidentiality": "NONE"}, "cvss-time": "2004-01-01T00:00:00Z", + "vulnerable_configuration": ["cpe:2.3:a:hp:dtmail", "cpe:2.3:a:university_of_washington:pine:4.02", "cpe:2.3:o:sco:unixware:7.0"], "id": "CVE-1999-0004", + "vulnerable_configuration_cpe_2_2": ["cpe:/a:hp:dtmail", "cpe:/a:university_of_washington:pine:4.02", "cpe:/o:sco:unixware:7.0"], + "summary": "MIME buffer overflow in email clients, e.g. Solaris mailtool and Outlook."} +cve_search_cve2 = {"cvss": 6, "Modified": "2008-09-09T08:33:31.007Z", "access": {"complexity": "LOW", "vector": "NETWORK", "authentication": "NONE"}, + "references": ["http://www.microsoft.com/technet/security/bulletin/ms98-008.asp"], "Published" : "1997-12-16T00:00:00Z", + "impact": {"availability": "PARTIAL", "integrity": "NONE", "confidentiality": "NONE"}, "cvss-time": "2004-01-01T00:00:00Z", + "vulnerable_configuration": ["cpe:2.3:a:hp:dtmail", "cpe:2.3:a:university_of_washington:pine:4.02", "cpe:2.3:o:sco:unixware:7.0"], "id": "CVE-2000-0005", + "vulnerable_configuration_cpe_2_2": ["cpe:/a:hp:dtmail", "cpe:/a:university_of_washington:pine:4.02", "cpe:/o:sco:unixware:7.0"], + "summary": "MIME buffer overflow in email clients, e.g. Solaris mailtool and Outlook."} +cve_search_cve3 = {"cvss": 7, "Modified": "2008-09-09T08:33:31.007Z", "access": {"complexity": "LOW", "vector": "NETWORK", "authentication": "NONE"}, + "references": ["http://www.microsoft.com/technet/security/bulletin/ms98-008.asp"], "Published" : "1997-12-16T00:00:00Z", + "impact": {"availability": "PARTIAL", "integrity": "NONE", "confidentiality": "NONE"}, "cvss-time": "2004-01-01T00:00:00Z", + "vulnerable_configuration": ["cpe:2.3:a:hp:dtmail", "cpe:2.3:a:university_of_washington:pine:4.02", "cpe:2.3:o:sco:unixware:7.0"], "id": "CVE-2001-0006", + "vulnerable_configuration_cpe_2_2": ["cpe:/a:hp:dtmail", "cpe:/a:university_of_washington:pine:4.02", "cpe:/o:sco:unixware:7.0"], + "summary": "MIME buffer overflow in email clients, e.g. Solaris mailtool and Outlook."} + +iva_cve1 = {"cpe_entries": [{"wfn": {"sw_edition": "ANY", "target_hw": "ANY", "version": "ANY", + "target_sw": "ANY", "vendor": "hp", "product": "dtmail", + "edition": "ANY", "language": "ANY", "part": "a", "update": "ANY", + "other": "ANY"}, "uri_binding": "cpe:/a:hp:dtmail"}, + {"wfn": {"sw_edition": "ANY", "target_hw": "ANY", "version": "7.0", + "target_sw": "ANY", "vendor": "sco", "product": "unixware", + "edition": "ANY", "language": "ANY", "part": "o", "update": "ANY", + "other": "ANY"}, "uri_binding": "cpe:/o:sco:unixware:7.0"}, + {"wfn": {"sw_edition": "ANY", "target_hw": "ANY", "version": "4.02", "target_sw": "ANY", + "vendor": "university_of_washington", "product": "pine", "edition": "ANY", + "language": "ANY", "part": "a", "update": "ANY", "other": "ANY"}, + "uri_binding": "cpe:/a:university_of_washington:pine:4.02"}], + "cve_summary": "MIME buffer overflow in email clients, e.g. Solaris mailtool and Outlook.", + "cve_id": "CVE-1999-0004"} +iva_cve2 = {"cpe_entries": [{"wfn": {"sw_edition": "ANY", "target_hw": "ANY", "version": "ANY", + "target_sw": "ANY", "vendor": "hp", "product": "dtmail", + "edition": "ANY", "language": "ANY", "part": "a", "update": "ANY", + "other": "ANY"}, "uri_binding": "cpe:/a:hp:dtmail"}, + {"wfn": {"sw_edition": "ANY", "target_hw": "ANY", "version": "7.0", + "target_sw": "ANY", "vendor": "sco", "product": "unixware", + "edition": "ANY", "language": "ANY", "part": "o", "update": "ANY", + "other": "ANY"}, "uri_binding": "cpe:/o:sco:unixware:7.0"}, + {"wfn": {"sw_edition": "ANY", "target_hw": "ANY", "version": "4.02", "target_sw": "ANY", + "vendor": "university_of_washington", "product": "pine", "edition": "ANY", + "language": "ANY", "part": "a", "update": "ANY", "other": "ANY"}, + "uri_binding": "cpe:/a:university_of_washington:pine:4.02"}], + "cve_summary": "MIME buffer overflow in email clients, e.g. Solaris mailtool and Outlook.", + "cve_id": "CVE-2000-0005"} +iva_cve3 = {"cpe_entries": [{"wfn": {"sw_edition": "ANY", "target_hw": "ANY", "version": "ANY", + "target_sw": "ANY", "vendor": "hp", "product": "dtmail", + "edition": "ANY", "language": "ANY", "part": "a", "update": "ANY", + "other": "ANY"}, "uri_binding": "cpe:/a:hp:dtmail"}, + {"wfn": {"sw_edition": "ANY", "target_hw": "ANY", "version": "7.0", + "target_sw": "ANY", "vendor": "sco", "product": "unixware", + "edition": "ANY", "language": "ANY", "part": "o", "update": "ANY", + "other": "ANY"}, "uri_binding": "cpe:/o:sco:unixware:7.0"}, + {"wfn": {"sw_edition": "ANY", "target_hw": "ANY", "version": "4.02", "target_sw": "ANY", + "vendor": "university_of_washington", "product": "pine", "edition": "ANY", + "language": "ANY", "part": "a", "update": "ANY", "other": "ANY"}, + "uri_binding": "cpe:/a:university_of_washington:pine:4.02"}], + "cve_summary": "MIME buffer overflow in email clients, e.g. Solaris mailtool and Outlook.", + "cve_id": "CVE-2001-0006"} + + +class TestCVEFeeds(unittest.TestCase): + + def setUp(self): + self.create_mongodb_client() + self.create_collections() + self.create_cve_db_object() + + def create_cve_db_object(self): + self.cve_feeds = patch_config_for('local_repositories.cve_feeds', 'CVEFeeds') + + def test_update_cves_feeds_when_iva_cve_collection_is_empty(self): + # insert CVE entries in cve-search database + self.insert_cves_in_cve_search_db() + # call method being tested + self.cve_feeds.update_cve_feeds() + # verify that CVE entries were read from cve-search database and inserted in IVA + # database according to the IVA format + self.verify_update() + + def test_update_cve_feeds_when_iva_cve_collection_is_not_empty(self): + # insert CVE entries in cve-search database + self.insert_cves_in_cve_search_db() + # insert CVE entries in IVA database + self.iva_cves_collection.insert_many(documents=[copy(iva_cve1), copy(iva_cve2)]) + # call method being tested + self.cve_feeds.update_cve_feeds() + # verify that CVE entries were read from cve-search database and inserted in IVA + # database according to the IVA format + self.verify_update() + + def test_update_batch_size(self): + for i in range(BATCH_SIZE + 5): + cve = copy(cve_search_cve1) + cve.update({'id': i}) + self.cve_search_cves_collection.insert_one(cve) + self.cve_feeds.update_cve_feeds() + self.assertEqual(self.iva_cves_collection.count(), BATCH_SIZE + 5) + + def verify_update(self): + dict_tester = DictTester() + updated_cves = self.get_updated_cves() + dict_tester.assertEqualKeys(iva_cve1, updated_cves[0]) + dict_tester.assertEqualKeys(iva_cve2, updated_cves[1]) + dict_tester.assertEqualKeys(iva_cve3, updated_cves[2]) + self.assertEqual(iva_cve1.get('cve_id'), updated_cves[2].get('cve_id')) + self.assertEqual(iva_cve2.get('cve_id'), updated_cves[1].get('cve_id')) + self.assertEqual(iva_cve3.get('cve_id'), updated_cves[0].get('cve_id')) + self.assertEqual(3, self.iva_cves_collection.count()) + + def get_updated_cves(self): + cves = list(self.iva_cves_collection.find()) + for cve in cves: + cve.pop('_id') + return cves + + def create_mongodb_client(self): + self.mongodb_client = MongoClient(DB_HOST, DB_PORT) + + def create_collections(self): + self.iva_db = self.mongodb_client[IVA_DB_NAME] + self.cve_search_db = self.mongodb_client[CVE_SEARCH_DB_NAME] + self.cve_search_cves_collection = self.cve_search_db[CVE_SEARCH_CVE_COLLECTION] + self.iva_cves_collection = self.iva_db[IVA_CVE_COLLECTION] + + def insert_cves_in_cve_search_db(self): + bulk = self.cve_search_cves_collection.initialize_ordered_bulk_op() + bulk.insert(cve_search_cve1) + bulk.insert(cve_search_cve2) + bulk.insert(cve_search_cve3) + bulk.execute() + pass + + def tearDown(self): + self.mongodb_client.drop_database(IVA_DB_NAME) + self.mongodb_client.drop_database(CVE_SEARCH_DB_NAME) + self.mongodb_client.close() + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_local_repositories/test_cve_search.py b/tests/test_local_repositories/test_cve_search.py new file mode 100644 index 0000000..e36a448 --- /dev/null +++ b/tests/test_local_repositories/test_cve_search.py @@ -0,0 +1,69 @@ +import unittest +from tests.mock_config import * +from local_repositories.cve_search import CVE_SEARCH_CPE_COLLECTION, CVE_SEARCH_CVE_COLLECTION +from pymongo import MongoClient + + +class TestCVESearchDB(unittest.TestCase): + + def setUp(self): + self.create_mongodb_client() + self.create_collections() + self.create_cve_search_db_object() + + def test_get_number_of_cves_entries_returns_4(self): + self.insert_cves() + self.assertEqual(4, self.cve_search_db.get_number_of_cves_entries()) + + def test_get_number_of_cves_entries_returns_0(self): + self.assertEqual(0, self.cve_search_db.get_number_of_cves_entries()) + + def test_get_number_of_cpes_entries_returns_4(self): + self.insert_cpes() + self.assertEqual(4, self.cve_search_db.get_number_of_cpes_entries()) + + def test_get_number_of_cpes_entries_returns_0(self): + self.assertEqual(0, self.cve_search_db.get_number_of_cpes_entries()) + + def test_is_cve_search_populated_returns_true(self): + self.insert_cpes() + self.insert_cves() + self.assertTrue(self.cve_search_db.is_cve_search_populated()) + + def test_is_cve_search_populated_returns_false_when_cpe_collection_empty(self): + self.insert_cves() + self.assertFalse(self.cve_search_db.is_cve_search_populated()) + + def test_is_cve_search_populated_returns_false_when_cve_collection_empty(self): + self.insert_cpes() + self.assertFalse(self.cve_search_db.is_cve_search_populated()) + + def test_is_cve_search_populated_returns_false(self): + self.assertFalse(self.cve_search_db.is_cve_search_populated()) + + def create_mongodb_client(self): + self.mongodb_client = MongoClient(DB_HOST, DB_PORT) + + def create_collections(self): + self.cve_search_db = self.mongodb_client[CVE_SEARCH_DB_NAME] + self.cve_search_cpe_collection = self.cve_search_db[CVE_SEARCH_CPE_COLLECTION] + self.cve_search_cve_collection = self.cve_search_db[CVE_SEARCH_CVE_COLLECTION] + + def create_cve_search_db_object(self): + self.cve_search_db = patch_config_for('local_repositories.cve_search', 'CVESearchDB') + + def insert_cves(self): + self.cve_search_cve_collection.insert_many(documents=[{'id': 'cve1'}, {'id': 'cve2'}, {'id': 'cve3'}, + {'id': 'cve4'}]) + + def insert_cpes(self): + self.cve_search_cpe_collection.insert_many(documents=[{'id': 'cpe1'}, {'id': 'cpe2'}, {'id': 'cpe3'}, + {'id': 'cpe4'}]) + + def tearDown(self): + self.mongodb_client.drop_database(CVE_SEARCH_DB_NAME) + self.mongodb_client.close() + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_local_repositories/test_iva_formatter.py b/tests/test_local_repositories/test_iva_formatter.py new file mode 100644 index 0000000..131b6b5 --- /dev/null +++ b/tests/test_local_repositories/test_iva_formatter.py @@ -0,0 +1,65 @@ +import unittest +from tests.dict_tester import DictTester +from local_repositories import iva_formatter + + +CVE_ID = "CVE-1999-0004" +SUMMARY = "MIME buffer overflow in email clients, e.g. Solaris mailtool and Outlook." +VUL_CONFIG = ["cpe:2.3:a:hp:dtmail", "cpe:2.3:a:university_of_washington:pine:4.02", "cpe:2.3:o:sco:unixware:7.0"] +VUL_CONFIG_CPE_2_2 = ["cpe:/a:hp:dtmail", "cpe:/a:university_of_washington:pine:4.02", "cpe:/o:sco:unixware:7.0"] +CVE_SEARCH_CVE = {"id": CVE_ID, "summary": SUMMARY, "Modified": "2008-09-09T08:33:31.007Z", + "Published": "1997-12-16T00:00:00Z", "cvss": 5, "cvss-time": "2004-01-01T00:00:00Z", + "access": {"complexity": "LOW", "vector": "NETWORK", "authentication": "NONE"}, + "references": ["http://www.microsoft.com/technet/security/bulletin/ms98-008.asp"], + "impact": {"availability": "PARTIAL", "integrity": "NONE", "confidentiality": "NONE"}, + "vulnerable_configuration": VUL_CONFIG, "vulnerable_configuration_cpe_2_2": VUL_CONFIG_CPE_2_2} +CVE_KEYS = {'cve_id': '', 'cve_summary': '', 'cpe_entries': ''} +CVE_SEARCH_CPE = {"id": "cpe:2.3:a:%240.99_kindle_books_project:%240.99_kindle_books:6:-:-:-:-:android", + "references": ["https://play.google.com/store/apps/details?id=com.kindle.books.for99"], + "cpe_2_2": "cpe:/a:%240.99_kindle_books_project:%240.99_kindle_books:6::~~~android~~", + "title": "$0.99 Kindle Books project $0.99 Kindle Books (aka com.kindle.books.for99) for android 6.0"} +IVA_CPE = {"wfn": {"sw_edition": "ANY", "target_hw": "ANY", "version": "6", "target_sw": "android", + "vendor": "$0.99_kindle_books_project", "product": "$0.99_kindle_books", "edition": "ANY", + "language": "ANY", "part": "a", "update": "ANY", "other": "ANY"}, + "formatted_string_binding": "cpe:2.3:a:%240.99_kindle_books_project:%240.99_kindle_books:6:-:-:-:-:android", + "uri_binding": "cpe:/a:%240.99_kindle_books_project:%240.99_kindle_books:6::~~~android~~"} + +class TestIVAFormatter(unittest.TestCase): + + def setUp(self): + self.dict_tester = DictTester() + + def test_format_cve(self): + formatted_cve = iva_formatter.format_cve(CVE_SEARCH_CVE) + self.dict_tester.assertEqualKeys(formatted_cve, CVE_KEYS) + self.assertEqual(formatted_cve.get('cve_id'), CVE_ID) + self.assertEqual(formatted_cve.get('cve_summary'), SUMMARY) + self.verify_cpe_entries(formatted_cve) + + def verify_cpe_entries(self, formatted_cve): + cpe_entries = formatted_cve.get('cpe_entries') + self.assertEqual(len(cpe_entries), 3) + self.verify_cpe_entry_has_correct_keys(cpe_entries) + self.verify_uri_bindings(cpe_entries) + + def verify_cpe_entry_has_correct_keys(self, cpe_entries): + self.dict_tester.assertEqualKeys(cpe_entries[0], {'uri_binding': '', 'wfn': ''}) + + def get_uri_bindings(self, cpe_entries): + uri_bindings = [] + for cpe in cpe_entries: + uri_bindings.append(cpe.get('uri_binding')) + return uri_bindings + + def verify_uri_bindings(self, cpe_entries): + uri_bindings = self.get_uri_bindings(cpe_entries) + for uri_binding in VUL_CONFIG_CPE_2_2: + self.assertTrue(uri_binding in uri_bindings) + + def test_format_cpe(self): + formatted_cpe = iva_formatter.format_cpe(CVE_SEARCH_CPE) + self.dict_tester.assertEqualKeys(formatted_cpe, IVA_CPE) + self.dict_tester.assertEqualValues(formatted_cpe, IVA_CPE) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_local_repositories/test_task_datetime_utils.py b/tests/test_local_repositories/test_task_datetime_utils.py new file mode 100644 index 0000000..cdb6fe0 --- /dev/null +++ b/tests/test_local_repositories/test_task_datetime_utils.py @@ -0,0 +1,49 @@ +import unittest +from datetime import datetime +from local_repositories.tasks import datetime_utils as time_utils + + +TIME_A = '14:01:00' +TIME_B = '14:00:00' +TIME = '10:04:07' +DATETIME_STR = '2016-12-21 10:04:07.769764' +DATETIME_STR_TOMORROW = '2016-12-22 10:04:07.769764' +FORMAT = '%Y-%m-%d %H:%M:%S.%f' +DATETIME = datetime.strptime(DATETIME_STR, FORMAT) + + +class TestDateTimeUtils(unittest.TestCase): + + def test_calculate_time_delta(self): + seconds = time_utils.calculate_delta_time(TIME_A, TIME_B) + self.assertEqual(60, seconds) + + seconds = time_utils.calculate_delta_time(TIME_B, TIME_A) + # one day minus 60 seconds + self.assertEqual(86340, seconds) + + def test_calculate_time_delta_returns_sixty_seconds_when_times_equal(self): + self.assertEqual(60, time_utils.calculate_delta_time(TIME_A, TIME_A)) + + def test_get_time_from_datetime(self): + self.assertEqual(TIME, time_utils.get_time_from_datetime(DATETIME)) + + def test_verify_time_format_returns_true(self): + self.assertTrue(time_utils.verify_time_format(TIME_A)) + self.assertTrue(time_utils.verify_time_format(TIME_B)) + + def test_verify_time_format_returns_false(self): + self.assertFalse(time_utils.verify_time_format('0415615')) + self.assertFalse(time_utils.verify_time_format('00:00')) + self.assertFalse(time_utils.verify_time_format('ab:cd:ef')) + + def test_add_one_day_to_datetime(self): + tomorrow = time_utils.add_one_day(DATETIME) + self.assertEqual(DATETIME_STR_TOMORROW, str(tomorrow)) + + def test_update_time_in_datetime(self): + updated_datetime = time_utils.update_time_in_datetime(DATETIME, TIME_A) + self.assertEqual(updated_datetime.strftime('%H:%M:%S'), TIME_A) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_local_repositories/test_task_info.py b/tests/test_local_repositories/test_task_info.py new file mode 100644 index 0000000..a81df53 --- /dev/null +++ b/tests/test_local_repositories/test_task_info.py @@ -0,0 +1,163 @@ +import unittest +from datetime import datetime +from pymongo import MongoClient +from local_repositories.tasks.task_info import TASKS_DB_COLLECTION +from tests.mock_config import * + +TASK_NAME = 'update_db' +DATETIME_STR = '2016-12-21 10:04:07.769000' +FORMAT = '%Y-%m-%d %H:%M:%S.%f' +DATETIME = datetime.strptime(DATETIME_STR, FORMAT) + + +class TestTaskDB(unittest.TestCase): + + def setUp(self): + self.mongodb_client = MongoClient(DB_HOST, DB_PORT) + self.test_db = self.mongodb_client[IVA_DB_NAME] + self.tasks_collection = self.test_db[TASKS_DB_COLLECTION] + self.create_update_cve_search_db_obj() + + def create_update_cve_search_db_obj(self): + self.task = patch_config_for('local_repositories.tasks.task_update_db', 'DailyUpdate') + + def test_create_task(self): + self.task.create_task_in_db() + + # verify create task + self.verify_new_created_task(self.tasks_collection.find_one({'name': TASK_NAME})) + + def test_task_is_not_created_if_exists(self): + # mock create task + self.tasks_collection.insert_one({'name': TASK_NAME}) + + # try to create already created task + self.task.create_task_in_db() + + # verify second task was not created + self.assertEqual(1, len(list(self.tasks_collection.find({'name': TASK_NAME})))) + + def test_update_execution_time(self): + # mock create task + self.tasks_collection.insert_one({'name': TASK_NAME, 'execution_time': '00:00:00'}) + + # update execution time + new_time = '14:00:00' + self.task.update_execution_time(new_time) + + # verify + self.assertEqual(self.get_task().get('execution_time'), new_time) + + def test_execution_time_is_not_updated_with_wrong_time_format(self): + # mock create task + self.tasks_collection.insert_one({'name': TASK_NAME, 'execution_time': '00:00:00'}) + + # update execution time with wrong format + self.task.update_execution_time('1456') + + # verify time was not updated + self.assertEqual(self.get_task().get('execution_time'), '00:00:00') + + def test_activate_task(self): + # mock create task + self.tasks_collection.insert_one({'name': TASK_NAME, 'execution_time': '00:00:00', 'status': 'inactive'}) + + # activate task + self.task.activate_task() + + # verify + self.assertEqual('active', self.get_task().get('status')) + + def test_deactivate_task(self): + # mock create task + self.tasks_collection.insert_one({'name': TASK_NAME, 'next_execution': DATETIME, 'status': 'active'}) + + # activate task + self.task.deactivate_task() + + # verify + task_dict = self.get_task() + self.assertEqual('inactive', task_dict.get('status')) + self.assertEqual('', task_dict.get('next_execution')) + + def test_activate_task_updates_next_execution(self): + # mock create task + self.tasks_collection.insert_one({'name': TASK_NAME, 'execution_time': '00:00:00', 'status': 'inactive', 'next_execution': ''}) + + # activate task + self.task.activate_task() + + # verify + self.assertEqual('active', self.get_task().get('status')) + self.assertFalse('' == self.get_task().get('next_execution')) + + def test_get_task(self): + # mock insert task + self.tasks_collection.insert_one({'name': TASK_NAME, 'execution_time': '00:00:00', 'status': 'inactive'}) + + # get task + task = self.task.get_task_info() + + # verify + self.assertIsNotNone(task) + + def test_get_task_returns_none(self): + # get task + task = self.task.get_task_info() + + # verify + self.assertIsNone(task) + + def test_update_task_next_execution(self): + # mock insert task + self.tasks_collection.insert_one({'name': TASK_NAME, 'execution_time': '00:00:00', 'next_execution': ''}) + + # update task's next execution + self.task.update_next_execution(DATETIME) + + # verify + self.assertEqual(DATETIME_STR, str(self.get_task().get('next_execution'))) + + def test_update_task_last_execution(self): + # mock insert task + self.tasks_collection.insert_one({'name': TASK_NAME, 'execution_time': '00:00:00', 'last_execution': ''}) + + # update task's next execution + now = datetime.now() + self.task.update_last_execution(now) + + # verify + self.assertEqual(now.date(), self.get_task().get('last_execution').date()) + self.assertEqual(now.hour, self.get_task().get('last_execution').hour) + + def test_is_task_active_returns_true(self): + # mock insert task + self.tasks_collection.insert_one({'name': TASK_NAME, 'execution_time': '00:00:00', 'status': 'active'}) + + self.assertTrue(self.task.is_task_active()) + + def test_is_task_active_returns_false(self): + # mock insert task + self.tasks_collection.insert_one({'name': TASK_NAME, 'execution_time': '00:00:00', 'status': 'inactive'}) + + self.assertFalse(self.task.is_task_active()) + + def test_is_task_active_returns_false_when_task_does_not_exist_in_db(self): + self.assertFalse(self.task.is_task_active()) + + def verify_new_created_task(self, created_task): + self.assertIsNotNone(created_task) + self.assertEqual('inactive', created_task.get('status')) + self.assertEqual('00:00:00', created_task.get('execution_time')) + self.assertEqual('', created_task.get('last_execution')) + self.assertEqual('', created_task.get('next_execution')) + + def get_task(self): + return self.tasks_collection.find_one({'name': TASK_NAME}) + + def tearDown(self): + self.mongodb_client.drop_database(IVA_DB_NAME) + self.mongodb_client.close() + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_local_repositories/test_task_scheduler.py b/tests/test_local_repositories/test_task_scheduler.py new file mode 100644 index 0000000..50185fc --- /dev/null +++ b/tests/test_local_repositories/test_task_scheduler.py @@ -0,0 +1,77 @@ +import time +import unittest +from pymongo import MongoClient +from tests.mock_config import * +from local_repositories.tasks.scheduler import TaskScheduler +from local_repositories.tasks.task_info import TASKS_DB_COLLECTION, TaskInfo + +TIMEOUT_FUNCTION = 'local_repositories.tasks.scheduler.time_utils.calculate_task_execution_timeout' +TASK_CLASS = 'local_repositories.tasks.scheduler.Task' + + +class TestTaskScheduler(unittest.TestCase): + + def setUp(self): + self.mongodb_client = MongoClient(DB_HOST, DB_PORT) + self.test_db = self.mongodb_client[IVA_DB_NAME] + self.tasks_collection = self.test_db[TASKS_DB_COLLECTION] + self.create_task_object() + + def create_task_object(self): + self.task = patch_config_for('tests.test_local_repositories.test_task_scheduler', 'MockTask') + + def test_task_scheduler_is_singleton_class(self): + self.assertEqual(id(TaskScheduler()), id(TaskScheduler())) + + def test_schedule_task(self): + self.task.create_task_in_db() + self.task.activate_task() + self.next_execution = self.task.get_task_info().get('next_execution') + self.last_execution = self.task.get_task_info().get('last_execution') + + with patch(TIMEOUT_FUNCTION, return_value=2): + scheduler = TaskScheduler() + scheduler.schedule_task(self.task) + self.assertTrue(scheduler.is_task_scheduled(self.task.name)) + self.assertFalse(self.next_execution == self.task.get_task_info().get('next_execution')) + # task was scheduled, every 2 seconds it is executed + time.sleep(3) + self.assertEqual(1, self.task.execution_calls) + self.assertFalse(self.last_execution == self.task.get_task_info().get('last_execution')) + self.last_execution = self.task.get_task_info().get('last_execution') + + time.sleep(2) + self.assertEqual(2, self.task.execution_calls) + self.assertFalse(self.last_execution == self.task.get_task_info().get('last_execution')) + + # we deactivate the task in order to stop the execution of the task + # since the task is inactive, it must not be scheduled + self.task.deactivate_task() + time.sleep(2) + self.assertFalse(scheduler.is_task_scheduled(self.task.name)) + + def test_task_is_not_scheduled_if_it_is_inactive(self): + self.task.create_task_in_db() + scheduler = TaskScheduler() + scheduler.schedule_task(self.task) + + self.assertEqual(0, self.task.execution_calls) + self.assertFalse(scheduler.is_task_scheduled(self.task.name)) + + def tearDown(self): + self.mongodb_client.drop_database(IVA_DB_NAME) + self.mongodb_client.close() + +if __name__ == '__main__': + unittest.main() + + +class MockTask(TaskInfo): + + def __init__(self): + super().__init__() + self.name = 'update_db' + self.execution_calls = 0 + + def execute(self): + self.execution_calls += 1 diff --git a/tests/test_user_authentication/__init__.py b/tests/test_user_authentication/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_user_authentication/test_user.py b/tests/test_user_authentication/test_user.py new file mode 100644 index 0000000..dd21f9d --- /dev/null +++ b/tests/test_user_authentication/test_user.py @@ -0,0 +1,217 @@ +import hashlib +import unittest + +from pymongo import MongoClient + +from tests.mock_config import * +from user_authentication.user import USER_DB_COLLECTION, USER_TYPES, UserExistsException + +NAME = 'test user' +SURNAME = 'test user surname' +USERNAME = 'admin' +PASSWORD = '123' + + +def get_hash_value(input_): + return hashlib.sha512(str.encode(input_)).hexdigest() + + +PWD_SHA512 = get_hash_value(PASSWORD) + + +class TestUser(unittest.TestCase): + + def setUp(self): + self.mongodb_client = MongoClient(DB_HOST, DB_PORT) + self.db = self.mongodb_client[IVA_DB_NAME] + self.user_collection = self.db[USER_DB_COLLECTION] + self.create_user_object() + + def create_user_object(self): + self.user = patch_config_for('user_authentication.user', 'User') + + def test_insert_user(self): + self.user.insert_new_user(NAME, SURNAME, USERNAME, PASSWORD) + + # verify + user = self.user_collection.find_one({'username': USERNAME, 'pwd_sha512': PWD_SHA512}) + self.verify_user(user) + + def test_user_is_not_inserted_when_exists(self): + # insert user + self.user_collection.insert_one({'username': USERNAME, 'pwd_sha512': PWD_SHA512}) + + # try to insert user that already exists + try: + self.user.insert_new_user(NAME, SURNAME, USERNAME, PASSWORD) + except UserExistsException as e: + self.assertEqual(USERNAME, e.user) + + # verify second user was not inserted + user = self.user_collection.find({'username': USERNAME, 'pwd_sha512': PWD_SHA512}) + self.assertEqual(1, len(list(user))) + + def test_get_user_from_db(self): + # insert user + self.user_collection.insert_one({'name': NAME, 'surname': SURNAME, 'username': USERNAME, 'pwd_sha512': PWD_SHA512}) + + # get user + user = self.user.get_user_from_db(USERNAME, PASSWORD) + + # verify + self.verify_user(user) + + def test_get_user_from_db_return_none(self): + self.assertIsNone(self.user.get_user_from_db(USERNAME, PASSWORD)) + + def test_verify_user_returns_true_when_user_exists_in_db(self): + # insert user + self.user_collection.insert_one({'username': USERNAME, 'pwd_sha512': PWD_SHA512}) + with patch('user_authentication.user.ldap_client.get_user_from_ldap_dir', return_value=None): + self.assertTrue(self.user.verify_user(USERNAME, PASSWORD)) + self.assertEqual(USER_TYPES.LOCAL, self.user.user_type) + + def test_verify_user_returns_true_when_user_exists_in_ldap(self): + with patch('user_authentication.user.ldap_client.config.get_ldap_base_dn', return_value='ou=users,dc=example,dc=com'): + with patch('user_authentication.user.ldap_client.get_user_from_ldap_dir', return_value='ldap_user'): + self.assertTrue(self.user.verify_user(USERNAME, PASSWORD)) + self.assertEqual(USER_TYPES.LDAP, self.user.user_type) + + def test_verify_user_returns_false_when_user_does_not_exits_neither_in_db_nor_in_ldap(self): + with patch('user_authentication.user.ldap_client.get_user_from_ldap_dir', return_value=None): + self.assertFalse(self.user.verify_user(USERNAME, PASSWORD)) + + def test_get_users_return_two_users(self): + # insert 2 users + self.user_collection.insert_many(documents=[{'username': 'user1', 'pwd_sha512': PWD_SHA512}, + {'username': 'user2', 'pwd_sha512': PWD_SHA512}]) + # get users + users = self.user.get_users() + + self.assertEqual(2, len(users)) + self.assertEqual('user1', users[0].get('username')) + self.assertEqual('user2', users[1].get('username')) + + def test_get_users_return_empty_list(self): + self.assertEqual(0, len(self.user.get_users())) + + def test_delete_user(self): + # insert user to be deleted + self.user_collection.insert_one({'username': USERNAME, 'pwd_sha512': PWD_SHA512}) + + # delete user + self.user.delete_user(USERNAME) + + self.assertIsNone(self.user_collection.find_one({'username': USERNAME})) + + def test_change_user_password_returns_true_when_pwd_was_changed(self): + # insert user + self.user_collection.insert_one({'username': USERNAME, 'pwd_sha512': PWD_SHA512}) + + # change pwd + new_pwd = '1234' + was_changed = self.user.change_password(USERNAME, PASSWORD, new_pwd) + + # verify + self.assertTrue(was_changed) + self.assertIsNotNone(self.user_collection.find_one({'username': USERNAME, 'pwd_sha512': get_hash_value(new_pwd)})) + + def test_change_user_password_returns_false_when_old_pwd_is_wrong(self): + # insert user + self.user_collection.insert_one({'username': USERNAME, 'pwd_sha512': PWD_SHA512}) + + # change pwd + new_pwd = '1234' + was_changed = self.user.change_password(USERNAME, 'wrong_pwd', new_pwd) + + # verify + self.assertFalse(was_changed) + self.assertIsNone(self.user_collection.find_one({'username': USERNAME, 'pwd_sha512': get_hash_value(new_pwd)})) + + def test_change_user_password_returns_false_when_new_pwd_is_empty(self): + # insert user + self.user_collection.insert_one({'username': USERNAME, 'pwd_sha512': PWD_SHA512}) + + # change pwd + new_pwd = '' + was_changed = self.user.change_password(USERNAME, 'wrong_pwd', new_pwd) + + # verify + self.assertFalse(was_changed) + self.assertIsNone(self.user_collection.find_one({'username': USERNAME, 'pwd_sha512': get_hash_value(new_pwd)})) + + def test_change_user_password_returns_false_when_new_pwd_is_none(self): + # insert user + self.user_collection.insert_one({'username': USERNAME, 'pwd_sha512': PWD_SHA512}) + + # change pwd + new_pwd = None + was_changed = self.user.change_password(USERNAME, 'wrong_pwd', new_pwd) + + # verify + self.assertFalse(was_changed) + + def test_modify_user(self): + # insert user to be modified + self.user_collection.insert_one({'name': NAME, 'surname': SURNAME, 'username': USERNAME, 'pwd_sha512': PWD_SHA512}) + + # modify user + new_name = 'modified name' + new_surname = 'modified surname' + new_username = 'modified username' + self.user.modify_user(USERNAME, new_username, new_name, new_surname) + + def test_user_is_not_modified_when_new_username_exists(self): + # insert two users + self.user_collection.insert_many(documents=[{'name': NAME, 'surname': SURNAME, 'username': USERNAME, 'pwd_sha512': PWD_SHA512}, + {'name': NAME, 'surname': SURNAME, 'username': 'username2', 'pwd_sha512': PWD_SHA512}]) + + # modify user + new_name = 'modified name' + new_surname = 'modified surname' + new_username = USERNAME + self.user.modify_user('username2', new_username, new_name, new_surname) + + # verify + user = self.user_collection.find_one({'username': 'username2'}) + self.assertIsNotNone(user) + self.assertEqual(NAME, user.get('name')) + self.assertEqual(SURNAME, user.get('surname')) + self.assertEqual('username2', user.get('username')) + + def test_modify_user_when_new_username_equal_to_old_username(self): + # insert two users + self.user_collection.insert_one({'name': NAME, 'surname': SURNAME, 'username': USERNAME, 'pwd_sha512': PWD_SHA512}) + + # modify user + new_name = 'modified name' + new_surname = 'modified surname' + self.user.modify_user(USERNAME, USERNAME, new_name, new_surname) + + # verify + user = self.user_collection.find_one({'username': USERNAME}) + self.assertIsNotNone(user) + self.assertEqual(new_name, user.get('name')) + self.assertEqual(new_surname, user.get('surname')) + self.assertEqual(USERNAME, user.get('username')) + + def test_is_user_collection_empty_returns_false(self): + self.user_collection.insert_one({'username': 'test'}) + self.assertFalse(self.user.is_user_collection_empty()) + + def test_is_user_collection_empty_returns_true(self): + self.assertTrue(self.user.is_user_collection_empty()) + + def verify_user(self, user): + self.assertIsNotNone(user) + self.assertEqual(NAME, user.get('name')) + self.assertEqual(SURNAME, user.get('surname')) + self.assertEqual(USERNAME, user.get('username')) + self.assertEqual(PWD_SHA512, user.get('pwd_sha512')) + + def tearDown(self): + self.mongodb_client.drop_database(IVA_DB_NAME) + self.mongodb_client.close() + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_wfn/__init__.py b/tests/test_wfn/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_wfn/test_decoder.py b/tests/test_wfn/test_decoder.py new file mode 100644 index 0000000..a25a3b5 --- /dev/null +++ b/tests/test_wfn/test_decoder.py @@ -0,0 +1,21 @@ +import unittest + +from wfn.encoding import Decoder + + +class TestDecoder(unittest.TestCase): + + def test_decode_non_alphanumeric_characters(self): + # {'%21': '!', '%22': '\"', '%23': '#', '%24': '$', '%25': '%', '%26': '&', '%27': '\'', + # '%28': '(', '%29': ')', '%2a': '*', '%2b': '+', '%2c': ',', + # '%2f': '/', '%3a': ':', '%3b': ';', '%3c': '<', '%3d': '=', '%3e': '>', '%3f': '?', + # '%40': '@', '%5b': '[', '%5c': '\\', '%5d': ']', '%5e': '^', '%60': '`', '%7b': '{', + # '%7c': '|', '%7d': '}', '%7e': '~', + # '%01': '?', '%02': '*'} + self.assertEqual('text!1text%!text', Decoder.decode_non_alphanumeric_characters('text%211text%25%21text')) + self.assertEqual('text!1text%!text\"text\"2text%\"text', Decoder.decode_non_alphanumeric_characters('text%211text%25%21text%22text%222text%25%22text')) + self.assertEqual('\"text\"2text%\"text]text]d_text%]]text', Decoder.decode_non_alphanumeric_characters('%22text%222text%25%22text%5dtext%5dd_text%25%5d%5dtext')) + self.assertEqual('\"text\"2text%\"text]text]d_text%]]text~e~text~e~', Decoder.decode_non_alphanumeric_characters('%22text%222text%25%22text%5dtext%5dd_text%25%5d%5dtext%7ee%7etext%7ee%7e')) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_wfn/test_edit_distance.py b/tests/test_wfn/test_edit_distance.py new file mode 100644 index 0000000..cc8e469 --- /dev/null +++ b/tests/test_wfn/test_edit_distance.py @@ -0,0 +1,17 @@ +import unittest +import editdistance +from matching.software_formatter import FormattedSoftware + +class MyTestCase(unittest.TestCase): + + def test_something(self): + software = {'vendor': 'Microsoft Corporation', + 'product': 'Microsoft .NET Framework 4.5.2', + 'version': '4.5.51209'} + formatted_software = FormattedSoftware(software) + # self.assertEqual(editdistance.eval('cpe:/a:microsoft:flash_player:9.0.115.0', + # 'cpe:/a:micro:flash_player:9.0.115.0'), 1) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_wfn/test_encoder.py b/tests/test_wfn/test_encoder.py new file mode 100644 index 0000000..4e4f43a --- /dev/null +++ b/tests/test_wfn/test_encoder.py @@ -0,0 +1,21 @@ +import unittest +from wfn.encoding import Encoder + + +class TestEncoder(unittest.TestCase): + + def test_decode_non_alphanumeric_characters(self): + # {'%21': '!', '%22': '\"', '%23': '#', '%24': '$', '%25': '%', '%26': '&', '%27': '\'', + # '%28': '(', '%29': ')', '%2a': '*', '%2b': '+', '%2c': ',', + # '%2f': '/', '%3a': ':', '%3b': ';', '%3c': '<', '%3d': '=', '%3e': '>', '%3f': '?', + # '%40': '@', '%5b': '[', '%5c': '\\', '%5d': ']', '%5e': '^', '%60': '`', '%7b': '{', + # '%7c': '|', '%7d': '}', '%7e': '~', + # '%01': '?', '%02': '*'} + self.assertEqual('text%211text%25%21text', Encoder.encode_non_alphanumeric_characters('text!1text%!text')) + self.assertEqual('text%211text%25%21text%22text%222text%25%22text', Encoder.encode_non_alphanumeric_characters('text!1text%!text\"text\"2text%\"text')) + self.assertEqual('%22text%222text%25%22text%5dtext%5dd_text%25%5d%5dtext', Encoder.encode_non_alphanumeric_characters('\"text\"2text%\"text]text]d_text%]]text')) + self.assertEqual('%22text%222text%25%22text%5dtext%5dd_text%25%5d%5dtext%7ee%7etext%7ee%7e', Encoder.encode_non_alphanumeric_characters('\"text\"2text%\"text]text]d_text%]]text~e~text~e~')) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_wfn/test_wfn_comparator.py b/tests/test_wfn/test_wfn_comparator.py new file mode 100644 index 0000000..ef1b0b3 --- /dev/null +++ b/tests/test_wfn/test_wfn_comparator.py @@ -0,0 +1,231 @@ +import unittest +from wfn.wfn_comparator import compare_wfn +from tests.dict_tester import DictTester + + +class MyTestCase(unittest.TestCase): + + def setUp(self): + self.dict_tester = DictTester() + + def test_compare_wfn_returns_100_percent_of_coincidence(self): + wfn_a = {'part': 'a', 'vendor': 'adobe', 'product': 'connect', 'version': '9.5', 'update': '*', + 'edition': '*', 'language': '*', 'sw_edition': '*', 'target_sw': '*', 'target_hw': '*', + 'other': '*'} + result = compare_wfn(wfn_a, wfn_a) + # verify + expected_result = {'coincidence_rate': 100, 'not_matches': []} + self.dict_tester.assertEqualKeys(expected_result, result) + self.dict_tester.assertEqualValues(expected_result, result) + + def test_compare_wfn_returns_0_percent_of_coincidence(self): + wfn_a = {'part': 'a', 'vendor': 'adobe', 'product': 'connect', 'version': '9.5', 'update': '*', + 'edition': '*', 'language': '*', 'sw_edition': '*', 'target_sw': '*', 'target_hw': '*', + 'other': '*'} + wfn_b = {'part': 'a', 'vendor': 'adobe_', 'product': 'connect_', 'version': '9.5.', 'update': '-', + 'edition': '-', 'language': '-', 'sw_edition': '-', 'target_sw': '-', 'target_hw': '-', + 'other': '-'} + result = compare_wfn(wfn_a, wfn_b) + # verify + not_matches = ['vendor', 'product', 'version', 'update', 'edition', 'language', 'sw_edition', 'target_sw', + 'target_hw', 'other'] + self.assertEqual(0, result.get('coincidence_rate')) + self.assertListEqual(sorted(not_matches), sorted(result.get('not_matches'))) + + def test_compare_wfn_returns_40_percent_of_coincidence(self): + wfn_a = {'part': 'a', 'vendor': 'adobe', 'product': 'connect', 'version': '9.5', 'update': '*', + 'edition': '*', 'language': '*', 'sw_edition': '*', 'target_sw': '*', 'target_hw': '*', + 'other': '*'} + wfn_b = {'part': 'a', 'vendor': 'adobe', 'product': 'connect', 'version': '9.5.', 'update': '-', + 'edition': '-', 'language': '-', 'sw_edition': '-', 'target_sw': '-', 'target_hw': '-', + 'other': '-'} + result = compare_wfn(wfn_a, wfn_b) + # verify + not_matches = ['version', 'update', 'edition', 'language', 'sw_edition', 'target_sw', 'target_hw', 'other'] + self.assertEqual(40, result.get('coincidence_rate')) + self.assertListEqual(sorted(not_matches), sorted(result.get('not_matches'))) + + def test_compare_wfn_returns_55_percent_of_coincidence(self): + wfn_a = {'part': 'a', 'vendor': 'adobe', 'product': 'connect', 'version': '9.5', 'update': '*', + 'edition': '*', 'language': '*', 'sw_edition': '*', 'target_sw': '*', 'target_hw': '*', + 'other': '*'} + wfn_b = {'part': 'a', 'vendor': 'adobe', 'product': 'connect', 'version': '9.5', 'update': '-', + 'edition': '-', 'language': '-', 'sw_edition': '-', 'target_sw': '-', 'target_hw': '-', + 'other': '-'} + result = compare_wfn(wfn_a, wfn_b) + # verify + not_matches = ['update', 'edition', 'language', 'sw_edition', 'target_sw', 'target_hw', 'other'] + self.assertEqual(55, result.get('coincidence_rate')) + self.assertListEqual(sorted(not_matches), sorted(result.get('not_matches'))) + + def test_compare_wfn_returns_75_percent_of_coincidence(self): + wfn_a = {'part': 'a', 'vendor': 'adobe', 'product': 'connect', 'version': '9.5', 'update': '*', + 'edition': '*', 'language': '*', 'sw_edition': '*', 'target_sw': '*', 'target_hw': '*', + 'other': '*'} + wfn_b = {'part': 'a', 'vendor': 'adobe', 'product': 'connect', 'version': '9.5', 'update': '*', + 'edition': '*', 'language': '-', 'sw_edition': '-', 'target_sw': '-', 'target_hw': '-', + 'other': '-'} + result = compare_wfn(wfn_a, wfn_b) + # verify + not_matches = ['language', 'sw_edition', 'target_sw', 'target_hw', 'other'] + self.assertEqual(75, result.get('coincidence_rate')) + self.assertListEqual(sorted(not_matches), sorted(result.get('not_matches'))) + + def test_compare_wfn_returns_95_percent_of_coincidence(self): + wfn_a = {'part': 'a', 'vendor': 'adobe', 'product': 'connect', 'version': '9.5', 'update': 'u96', + 'edition': '2016', 'language': '*', 'sw_edition': '*', 'target_sw': '*', 'target_hw': '*', + 'other': '*'} + wfn_b = {'part': 'a', 'vendor': 'adobe', 'product': 'connect', 'version': '9.5', 'update': 'u96', + 'edition': '2016', 'language': '*', 'sw_edition': '*', 'target_sw': '*', 'target_hw': '*', + 'other': '-'} + result = compare_wfn(wfn_a, wfn_b) + # verify + not_matches = ['other'] + self.assertEqual(95, result.get('coincidence_rate')) + self.assertListEqual(sorted(not_matches), sorted(result.get('not_matches'))) + + def test_compare_wfn_returns_25_percent_of_coincidence(self): + wfn_a = {'part': 'a', 'vendor': 'adobe_', 'product': 'connect_', 'version': '9.5.', 'update': 'u96_', + 'edition': '2016_', 'language': 'ANY', 'sw_edition': 'ANY', 'target_sw': 'ANY', 'target_hw': 'ANY', + 'other': 'ANY'} + wfn_b = {'vendor': 'adobe', 'product': 'connect', 'version': '9.5', 'update': 'u96', + 'edition': '2016', 'language': '*', 'sw_edition': '*', 'target_sw': '*', 'target_hw': '*', + 'other': '*'} + result = compare_wfn(wfn_a, wfn_b) + # verify + not_matches = ['vendor', 'product', 'version', 'update', 'edition'] + self.assertEqual(25, result.get('coincidence_rate')) + self.assertListEqual(sorted(not_matches), sorted(result.get('not_matches'))) + + def test_compare_wfn_any_case(self): + wfn_a = {'part': 'a', 'vendor': 'adobe', 'product': 'connect', 'version': '9.5', 'update': 'u96', + 'edition': '2016', 'language': 'ANY', 'sw_edition': 'ANY', 'target_sw': 'ANY', 'target_hw': 'ANY', + 'other': 'ANY'} + wfn_b = {'vendor': 'adobe', 'product': 'connect', 'version': '9.5', 'update': 'u96', + 'edition': '2016', 'language': 'DE', 'sw_edition': '2015', 'target_sw': 'windows', 'target_hw': 'x85', + 'other': 'bla'} + result = compare_wfn(wfn_a, wfn_b) + # verify + self.assertEqual(100, result.get('coincidence_rate')) + self.assertEqual([], result.get('not_matches')) + + # ANY value with asterisk + wfn_a = {'part': 'a', 'vendor': 'adobe', 'product': 'connect', 'version': '9.5.2', 'update': 'u96', + 'edition': '2016', 'language': '*', 'sw_edition': '*', 'target_sw': '*', 'target_hw': '*', + 'other': '*'} + wfn_b = {'part': 'a', 'vendor': 'adobe', 'product': 'connect', 'version': '9.5', 'update': '*', + 'edition': '*', 'language': '*', 'sw_edition': '*', 'target_sw': '*', 'target_hw': '*', + 'other': '*'} + result = compare_wfn(wfn_a, wfn_b) + # verify + not_matches = ['version'] + self.assertEqual(85, result.get('coincidence_rate')) + self.assertListEqual(not_matches, result.get('not_matches')) + + wfn_a = {'part': 'a', 'vendor': 'adobe', 'product': 'connect', 'version': '9.5', 'update': 'u96', + 'edition': '2016', 'language': 'ANY', 'sw_edition': 'ANY', 'target_sw': 'ANY', 'target_hw': 'ANY', + 'other': '*'} + wfn_b = {'part': 'a', 'vendor': 'adobe', 'product': 'connect', 'version': '9.5', 'update': 'ANY', + 'edition': 'ANY', 'language': 'ANY', 'sw_edition': 'ANY', 'target_sw': 'ANY', 'target_hw': 'ANY', + 'other': '-'} + result = compare_wfn(wfn_a, wfn_b) + # verify ANY is not equal to - + not_matches = ['other'] + self.assertEqual(95, result.get('coincidence_rate')) + self.assertListEqual(not_matches, result.get('not_matches')) + + wfn_b.update({'other': 'NA'}) + # verify ANY is not equal to NA + not_matches = ['other'] + self.assertEqual(95, result.get('coincidence_rate')) + self.assertListEqual(not_matches, result.get('not_matches')) + + def test_compare_wfn_version_equal(self): + wfn_a = {'part': 'a', 'vendor': 'adobe', 'product': 'connect', 'version': '9.*', 'update': '*', + 'edition': '*', 'language': '*', 'sw_edition': '*', 'target_sw': '*', 'target_hw': '*', + 'other': '*'} + wfn_b = {'part': 'a', 'vendor': 'adobe', 'product': 'connect', 'version': '9', 'update': '*', + 'edition': '*', 'language': '*', 'sw_edition': '*', 'target_sw': '*', 'target_hw': '*', + 'other': '*'} + self.compare_and_verify_equal_version(wfn_a, wfn_b) + + wfn_a.update({'version': '9.1.*'}) + wfn_b.update({'version': '9.1.5'}) + self.compare_and_verify_equal_version(wfn_a, wfn_b) + + wfn_a.update({'version': '9.1.2.3.4'}) + wfn_b.update({'version': '9.*'}) + self.compare_and_verify_equal_version(wfn_a, wfn_b) + + wfn_a.update({'version': '9.1.2.3.4'}) + wfn_b.update({'version': '9.1.*'}) + self.compare_and_verify_equal_version(wfn_a, wfn_b) + + wfn_a.update({'version': '9.1.2.3.4'}) + wfn_b.update({'version': '9.1.2.*'}) + self.compare_and_verify_equal_version(wfn_a, wfn_b) + + wfn_a.update({'version': '9.1.2.3.4'}) + wfn_b.update({'version': '9.1.2.3.*'}) + self.compare_and_verify_equal_version(wfn_a, wfn_b) + + wfn_a.update({'version': '9.1.*'}) + wfn_b.update({'version': '9.1.2.3.*'}) + self.compare_and_verify_equal_version(wfn_a, wfn_b) + + wfn_a.update({'version': '9.1.2.3.*'}) + wfn_b.update({'version': '9.1.*'}) + self.compare_and_verify_equal_version(wfn_a, wfn_b) + + wfn_a.update({'version': '9.*.2.3'}) + wfn_b.update({'version': '9.9.2.3'}) + self.compare_and_verify_equal_version(wfn_a, wfn_b) + + wfn_a.update({'version': '2008'}) + wfn_b.update({'version': '2008'}) + self.compare_and_verify_equal_version(wfn_a, wfn_b) + + def compare_and_verify_equal_version(self, wfn_a, wfn_b): + result = compare_wfn(wfn_a, wfn_b) + # verify + self.assertEqual(100, result.get('coincidence_rate')) + self.assertListEqual([], result.get('not_matches')) + + def test_compare_wfn_version_not_equal(self): + wfn_a = {'part': 'a', 'vendor': 'adobe', 'product': 'connect', 'version': '9.1.*', 'update': '*', + 'edition': '*', 'language': '*', 'sw_edition': '*', 'target_sw': '*', 'target_hw': '*', + 'other': '*'} + wfn_b = {'part': 'a', 'vendor': 'adobe', 'product': 'connect', 'version': '9', 'update': '*', + 'edition': '*', 'language': '*', 'sw_edition': '*', 'target_sw': '*', 'target_hw': '*', + 'other': '*'} + self.compare_and_verify_not_equal_version(wfn_a, wfn_b) + + wfn_a.update({'version': '9.2.3.4.*'}) + wfn_b.update({'version': '9.1.*'}) + self.compare_and_verify_not_equal_version(wfn_a, wfn_b) + + wfn_a.update({'version': '9.1.*'}) + wfn_b.update({'version': '9.2.3.4.*'}) + self.compare_and_verify_not_equal_version(wfn_a, wfn_b) + + wfn_a.update({'version': '9.*.1.3'}) + wfn_b.update({'version': '9.5.1.2'}) + self.compare_and_verify_not_equal_version(wfn_a, wfn_b) + + wfn_a.update({'version': '9.*.1.3'}) + wfn_b.update({'version': '9.5.1.3.8'}) + self.compare_and_verify_not_equal_version(wfn_a, wfn_b) + + wfn_a.update({'version': '9.*.1.3.8.9'}) + wfn_b.update({'version': '9.*.1.3.8'}) + self.compare_and_verify_not_equal_version(wfn_a, wfn_b) + + def compare_and_verify_not_equal_version(self, wfn_a, wfn_b): + result = compare_wfn(wfn_a, wfn_b) + # verify + self.assertEqual(85, result.get('coincidence_rate')) + self.assertListEqual(['version'], result.get('not_matches')) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_wfn/test_wfn_converter.py b/tests/test_wfn/test_wfn_converter.py new file mode 100644 index 0000000..c71588a --- /dev/null +++ b/tests/test_wfn/test_wfn_converter.py @@ -0,0 +1,313 @@ +import unittest +from tests.dict_tester import DictTester +from wfn.wfn_converter import WFNConverter + + +class TestWFNConverter(unittest.TestCase): + + def setUp(self): + self.test_utils = DictTester() + self.wfn_converter = WFNConverter() + + def test_converter_cpe_uri_to_wfn(self): + # taken from CPE-Naming.pdf + # Example 1 + # cpe:/a:microsoft:internet_explorer:8.0.6001:beta + # wfn:[part="a",vendor="microsoft",product="internet_explorer", version="8\.0\.6001",update="beta",edition=ANY, language=ANY] + + cpe_uri = 'cpe:/a:microsoft:internet_explorer:8.0.6001:beta' + + expected_wfn_document = {'part': 'a', 'vendor': 'microsoft', 'product': 'internet_explorer', + 'version': '8.0.6001', 'update': 'beta', 'edition': 'ANY', 'language': 'ANY', + 'sw_edition': 'ANY', 'target_sw': 'ANY', 'target_hw': 'ANY', 'other': 'ANY'} + wfn = self.wfn_converter.convert_cpe_uri_to_wfn(cpe_uri) + self.assert_wfn_docs_equal(expected_wfn_document, wfn) + + # normally, double points (:) are encoded to (%3a). However, if for some reason the encoding was not performed + # during the creation of the CPE URI, we have to consider this case and create the corresponding WFN correctly. + cpe_uri = 'cpe:/a:string_value_with\:double_points:internet_explorer:8.0.6001:beta' + expected_wfn_document = {'part': 'a', 'vendor': 'string_value_with:double_points', 'product': 'internet_explorer', + 'version': '8.0.6001', 'update': 'beta', 'edition': 'ANY', 'language': 'ANY', + 'sw_edition': 'ANY', 'target_sw': 'ANY', 'target_hw': 'ANY', 'other': 'ANY'} + + wfn = self.wfn_converter.convert_cpe_uri_to_wfn(cpe_uri) + self.assert_wfn_docs_equal(expected_wfn_document, wfn) + + # Example 2 + # cpe:/a:microsoft:internet_explorer:8.%2a:sp%3f + # wfn:[part="a",vendor="microsoft",product="internet_explorer", version="8\.\*",update="sp\?",edition=ANY,language=ANY] + cpe_uri = 'cpe:/a:microsoft:internet_explorer:8.%2a:sp%3f' + expected_wfn_document = {'part': 'a', 'vendor': 'microsoft', 'product': 'internet_explorer', + 'version': '8.*', 'update': 'sp?', 'edition': 'ANY', 'language': 'ANY', + 'sw_edition': 'ANY', 'target_sw': 'ANY', 'target_hw': 'ANY', 'other': 'ANY'} + + wfn = self.wfn_converter.convert_cpe_uri_to_wfn(cpe_uri) + self.assert_wfn_docs_equal(expected_wfn_document, wfn) + + # Example 3 + # cpe:/a:microsoft:internet_explorer:8.%02:sp%01 + # wfn:[part="a",vendor="microsoft",product="internet_explorer", version="8\.*",update="sp?",edition=ANY,language=ANY] + cpe_uri = 'cpe:/a:microsoft:internet_explorer:8.%02:sp%01' + expected_wfn_document = {'part': 'a', 'vendor': 'microsoft', 'product': 'internet_explorer', + 'version': '8.*', 'update': 'sp?', 'edition': 'ANY', 'language': 'ANY', + 'sw_edition': 'ANY', 'target_sw': 'ANY', 'target_hw': 'ANY', 'other': 'ANY'} + + wfn = self.wfn_converter.convert_cpe_uri_to_wfn(cpe_uri) + self.assert_wfn_docs_equal(expected_wfn_document, wfn) + + # Example 4 + # cpe:/a:hp:insight_diagnostics:7.4.0.1570::~~online~win2003~x64~ + # wfn:[part="a",vendor="hp",product="insight_diagnostics", version="7\.4\.0\.1570",update=ANY,edition=ANY, sw_edition="online",target_sw="win2003",target_hw="x64", other=ANY,language=ANY] + cpe_uri = 'cpe:/a:hp:insight_diagnostics:7.4.0.1570::~~online~win2003~x64~' + expected_wfn_document = {'part': 'a', 'vendor': 'hp', 'product': 'insight_diagnostics', + 'version': '7.4.0.1570', 'update': 'ANY', 'edition': 'ANY', 'language': 'ANY', + 'sw_edition': 'online', 'target_sw': 'win2003', 'target_hw': 'x64', 'other': 'ANY'} + + wfn = self.wfn_converter.convert_cpe_uri_to_wfn(cpe_uri) + self.assert_wfn_docs_equal(expected_wfn_document, wfn) + + # normally, tildes (~) are encoded to (%7e). However, if for some reason encoding was not performed + # during the creation of the CPE URI, we have to consider this case and create the corresponding WFN correctly. + + cpe_uri = 'cpe:/a:hp:string_value_with\~:7.4.0.1570::~~online~win2003~x64~' + expected_wfn_document = {'part': 'a', 'vendor': 'hp', 'product': 'string_value_with~', + 'version': '7.4.0.1570', 'update': 'ANY', 'edition': 'ANY', 'language': 'ANY', + 'sw_edition': 'online', 'target_sw': 'win2003', 'target_hw': 'x64', 'other': 'ANY'} + + wfn = self.wfn_converter.convert_cpe_uri_to_wfn(cpe_uri) + self.assert_wfn_docs_equal(expected_wfn_document, wfn) + + # Example 5 + # cpe:/a:hp:openview_network_manager:7.51:-:~~~linux~~ + # wfn:[part="a",vendor="hp",product="openview_network_manager", version="7\.51",update=NA,edition=ANY,sw_edition=ANY, target_sw="linux",target_HW=ANY,other=ANY,language=ANY] + + cpe_uri = 'cpe:/a:hp:openview_network_manager:7.51:-:~~~linux~~' + expected_wfn_document = {'part': 'a', 'vendor': 'hp', 'product': 'openview_network_manager', + 'version': '7.51', 'update': 'NA', 'edition': 'ANY', 'language': 'ANY', + 'sw_edition': 'ANY', 'target_sw': 'linux', 'target_hw': 'ANY', 'other': 'ANY'} + + wfn = self.wfn_converter.convert_cpe_uri_to_wfn(cpe_uri) + self.assert_wfn_docs_equal(expected_wfn_document, wfn) + + # Example 6 + # cpe:/a:foo%5cbar:big%24money_2010%07:::~~special~ipod_touch~80gb~ + + cpe_uri = 'cpe:/a:foo%5cbar:big%24money_2010%07:::~~special~ipod_touch~80gb~' + expected_wfn_document = {'part': 'a', 'vendor': 'foo\\bar', 'product': 'big$money_2010%07', + 'version': 'ANY', 'update': 'ANY', 'edition': 'ANY', 'language': 'ANY', + 'sw_edition': 'special', 'target_sw': 'ipod_touch', 'target_hw': '80gb', 'other': 'ANY'} + + wfn = self.wfn_converter.convert_cpe_uri_to_wfn(cpe_uri) + self.assert_wfn_docs_equal(expected_wfn_document, wfn) + + # Example 7 + # cpe:/a:foo~bar:big%7emoney_2010 + # wfn:[part="a",vendor="foo\~bar",product="big\~money_2010", version=ANY,update=ANY,edition=ANY,language=ANY] + + cpe_uri = 'cpe:/a:foo~bar:big%7emoney_2010' + expected_wfn_document = {'part': 'a', 'vendor': 'foo~bar', 'product': 'big~money_2010', + 'version': 'ANY', 'update': 'ANY', 'edition': 'ANY', 'language': 'ANY', + 'sw_edition': 'ANY', 'target_sw': 'ANY', 'target_hw': 'ANY', 'other': 'ANY'} + + wfn = self.wfn_converter.convert_cpe_uri_to_wfn(cpe_uri) + self.assert_wfn_docs_equal(expected_wfn_document, wfn) + + # Example 8 + # cpe:/a:foo:bar:12.%02.1234 + + cpe_uri = 'cpe:/a:foo:bar:12.%02.1234' + expected_wfn_document = {'part': 'a', 'vendor': 'foo', 'product': 'bar', + 'version': '12.*.1234', 'update': 'ANY', 'edition': 'ANY', 'language': 'ANY', + 'sw_edition': 'ANY', 'target_sw': 'ANY', 'target_hw': 'ANY', 'other': 'ANY'} + + wfn = self.wfn_converter.convert_cpe_uri_to_wfn(cpe_uri) + self.assert_wfn_docs_equal(expected_wfn_document, wfn) + + # taken from official-cpe-dictionary_v2.3.xml + + cpe_uri = 'cpe:/a:mcafee:advanced_threat_defense:3.4.4.14' + expected_wfn_document = {'part': 'a', 'vendor': 'mcafee', 'product': 'advanced_threat_defense', + 'version': '3.4.4.14', 'update': 'ANY', 'edition': 'ANY', 'language': 'ANY', + 'sw_edition': 'ANY', 'target_sw': 'ANY', 'target_hw': 'ANY', 'other': 'ANY'} + + wfn = self.wfn_converter.convert_cpe_uri_to_wfn(cpe_uri) + self.assert_wfn_docs_equal(expected_wfn_document, wfn) + + cpe_uri = 'cpe:/a:mbtcreations:detox_juicing_diet_recipes:1.1::~~~android~~' + expected_wfn_document = {'part': 'a', 'vendor': 'mbtcreations', 'product': 'detox_juicing_diet_recipes', + 'version': '1.1', 'update': 'ANY', 'edition': 'ANY', 'sw_edition': 'ANY', + 'target_sw': 'android', 'target_hw': 'ANY', 'other': 'ANY', 'language': 'ANY'} + + wfn = self.wfn_converter.convert_cpe_uri_to_wfn(cpe_uri) + self.assert_wfn_docs_equal(expected_wfn_document, wfn) + + cpe_uri = 'cpe:/a:lemonldap-ng:lemonldap%3a%3a:1.0:rc2' + expected_wfn_document = {'part': 'a', 'vendor': 'lemonldap-ng', 'product': 'lemonldap::', + 'version': '1.0', 'update': 'rc2', 'edition': 'ANY', 'sw_edition': 'ANY', + 'target_sw': 'ANY', 'target_hw': 'ANY', 'other': 'ANY', 'language': 'ANY'} + + wfn = self.wfn_converter.convert_cpe_uri_to_wfn(cpe_uri) + self.assert_wfn_docs_equal(expected_wfn_document, wfn) + + def test_convert_cpe_uri_to_wfn_no_edition_special_case(self): + cpe_uri = 'cpe:/a:vendor:product:version:update:edition:language' + + expected_wfn_document = {'part': 'a', 'vendor': 'vendor', 'product': 'product', + 'version': 'version', 'update': 'update', 'edition': 'edition', 'language': 'language', + 'sw_edition': 'ANY', 'target_sw': 'ANY', 'target_hw': 'ANY', 'other': 'ANY'} + wfn = self.wfn_converter.convert_cpe_uri_to_wfn(cpe_uri) + self.assert_wfn_docs_equal(expected_wfn_document, wfn) + + cpe_uri = 'cpe:/a:vendor:product:version:update::language' + + expected_wfn_document = {'part': 'a', 'vendor': 'vendor', 'product': 'product', + 'version': 'version', 'update': 'update', 'edition': 'ANY', 'language': 'language', + 'sw_edition': 'ANY', 'target_sw': 'ANY', 'target_hw': 'ANY', 'other': 'ANY'} + + wfn = self.wfn_converter.convert_cpe_uri_to_wfn(cpe_uri) + self.assert_wfn_docs_equal(expected_wfn_document, wfn) + + cpe_uri = 'cpe:/a:vendor:product:version:update:edition' + + expected_wfn_document = {'part': 'a', 'vendor': 'vendor', 'product': 'product', + 'version': 'version', 'update': 'update', 'edition': 'edition', 'language': 'ANY', + 'sw_edition': 'ANY', 'target_sw': 'ANY', 'target_hw': 'ANY', 'other': 'ANY'} + + wfn = self.wfn_converter.convert_cpe_uri_to_wfn(cpe_uri) + self.assert_wfn_docs_equal(expected_wfn_document, wfn) + + def test_convert_cpe_uri_to_wfn_edition_special_case(self): + cpe_uri = 'cpe:/a:vendor:product:version:update:~edition~~~~' + + expected_wfn_document = {'part': 'a', 'vendor': 'vendor', 'product': 'product', + 'version': 'version', 'update': 'update', 'edition': 'edition', 'language': 'ANY', + 'sw_edition': 'ANY', 'target_sw': 'ANY', 'target_hw': 'ANY', 'other': 'ANY'} + wfn = self.wfn_converter.convert_cpe_uri_to_wfn(cpe_uri) + self.assert_wfn_docs_equal(expected_wfn_document, wfn) + + cpe_uri = 'cpe:/a:vendor:product:version:update:language~edition~~~~' + + expected_wfn_document = {'part': 'a', 'vendor': 'vendor', 'product': 'product', + 'version': 'version', 'update': 'update', 'edition': 'edition', 'language': 'language', + 'sw_edition': 'ANY', 'target_sw': 'ANY', 'target_hw': 'ANY', 'other': 'ANY'} + wfn = self.wfn_converter.convert_cpe_uri_to_wfn(cpe_uri) + self.assert_wfn_docs_equal(expected_wfn_document, wfn) + + cpe_uri = 'cpe:/a:vendor:product:version:-:language~edition~~~~' + + expected_wfn_document = {'part': 'a', 'vendor': 'vendor', 'product': 'product', + 'version': 'version', 'update': 'NA', 'edition': 'edition', 'language': 'language', + 'sw_edition': 'ANY', 'target_sw': 'ANY', 'target_hw': 'ANY', 'other': 'ANY'} + wfn = self.wfn_converter.convert_cpe_uri_to_wfn(cpe_uri) + self.assert_wfn_docs_equal(expected_wfn_document, wfn) + + cpe_uri = 'cpe:/a:vendor:product:version:update:language~edition~sw_edition~target_sw~target_hw~other' + + expected_wfn_document = {'part': 'a', 'vendor': 'vendor', 'product': 'product', + 'version': 'version', 'update': 'update', 'edition': 'edition', 'language': 'language', + 'sw_edition': 'sw_edition', 'target_sw': 'target_sw', 'target_hw': 'target_hw', 'other': 'other'} + wfn = self.wfn_converter.convert_cpe_uri_to_wfn(cpe_uri) + self.assert_wfn_docs_equal(expected_wfn_document, wfn) + + def test_convert_wfn_to_cpe_uri_binding(self): + # example 1 (pag. 2. CPE: Naming Specification Version) + # wfn:[part="a",vendor="microsoft",product="internet_explorer", version="8\.0\.6001",update="beta",edition=ANY] + wfn = {'part': 'a', 'vendor': 'microsoft', 'product': 'internet_explorer', 'version': '8.0.6001', + 'update': 'beta', 'edition': 'ANY', 'sw_edition': 'ANY', 'target_sw': 'ANY', 'target_hw': 'ANY', + 'other': 'ANY', 'language': 'ANY'} + expected_uri = 'cpe:/a:microsoft:internet_explorer:8.0.6001:beta' + self.assertEqual(expected_uri, self.wfn_converter.convert_wfn_to_uri(wfn)) + + # example 3 (pag. 2. CPE: Naming Specification Version) + # wfn:[part="a",vendor="hp",product="insight_diagnostics", version="7\.4\.0\.1570",update=NA, + # sw_edition="online",target_sw="win2003",target_hw="x64"] + + wfn = {'part': 'a', 'vendor': 'hp', 'product': 'insight_diagnostics', 'version': '7.4.0.1570', + 'update': 'NA', 'edition': 'ANY', 'sw_edition': 'online', 'target_sw': 'win2003', 'target_hw': 'x64', + 'other': 'ANY', 'language': 'ANY'} + + expected_uri = 'cpe:/a:hp:insight_diagnostics:7.4.0.1570:-:~~online~win2003~x64~' + self.assertEqual(expected_uri, self.wfn_converter.convert_wfn_to_uri(wfn)) + + # example 4 (pag. 2. CPE: Naming Specification Version) + # wfn:[part="a",vendor="hp",product="openview_network_manager", version="7\.51",target_sw="linux"] + + wfn = {'part': 'a', 'vendor': 'hp', 'product': 'openview_network_manager', 'version': '7.51', + 'update': 'ANY', 'edition': 'ANY', 'sw_edition': 'ANY', 'target_sw': 'linux', 'target_hw': 'ANY', + 'other': 'ANY', 'language': 'ANY'} + + expected_uri = 'cpe:/a:hp:openview_network_manager:7.51::~~~linux~~' + self.assertEqual(expected_uri, self.wfn_converter.convert_wfn_to_uri(wfn)) + + # example 5 (pag. 2. CPE: Naming Specification Version) + # wfn:[part="a",vendor="foo\\bar",product="big\$money_manager_2010", sw_edition="special",target_sw="ipod_touch",target_hw="80gb"] + + wfn = {'part': 'a', 'vendor': 'foo\\bar', 'product': 'big$money_manager_2010', 'version': 'ANY', + 'update': 'ANY', 'edition': 'ANY', 'sw_edition': 'special', 'target_sw': 'ipod_touch', + 'target_hw': '80gb', 'other': 'ANY', 'language': 'ANY'} + + expected_uri = 'cpe:/a:foo%5cbar:big%24money_manager_2010:::~~special~ipod_touch~80gb~' + self.assertEqual(expected_uri, self.wfn_converter.convert_wfn_to_uri(wfn)) + + + def test_convert_wfn_with_special_chars_to_cpe_uri_binding(self): + # example 2 (pag. 2. CPE: Naming Specification Version) + # wfn:[part="a",vendor="microsoft",product="internet_explorer", version="8\.*",update="sp?"] + # Special Chars in WFN: * amd ? + # The CPE specification assigns different codes to /* and * and /? and ?. Since we do not use / to scape + # characters, the characters * and ? have each one two possible codes. + + wfn = {'part': 'a', 'vendor': 'microsoft', 'product': 'internet_explorer', 'version': '8.*', + 'update': 'sp?', 'edition': 'ANY', 'sw_edition': 'ANY', 'target_sw': 'ANY', 'target_hw': 'ANY', + 'other': 'ANY', 'language': 'ANY'} + expected_uri_1 = 'cpe:/a:microsoft:internet_explorer:8.%02:sp%01' + expected_uri_2 = 'cpe:/a:microsoft:internet_explorer:8.%2a:sp%01' + expected_uri_3 = 'cpe:/a:microsoft:internet_explorer:8.%02:sp%3f' + expected_uri_4 = 'cpe:/a:microsoft:internet_explorer:8.%2a:sp%3f' + + assert_1 = expected_uri_1 == self.wfn_converter.convert_wfn_to_uri(wfn) + assert_2 = expected_uri_2 == self.wfn_converter.convert_wfn_to_uri(wfn) + assert_3 = expected_uri_3 == self.wfn_converter.convert_wfn_to_uri(wfn) + assert_4 = expected_uri_4 == self.wfn_converter.convert_wfn_to_uri(wfn) + self.assertTrue(assert_1 or assert_2 or assert_3 or assert_4) + + def test_convert_wfn_to_cpe_uri_binding_special_case(self): + wfn = {'part': 'a', 'vendor': 'microsoft', 'product': 'internet_explorer', 'version': '8.0.6001', + 'update': 'beta', 'edition': 'edition_value', 'sw_edition': 'ANY', 'target_sw': 'windows', + 'target_hw': 'ANY', 'other': 'ANY', 'language': 'ANY'} + expected_uri = 'cpe:/a:microsoft:internet_explorer:8.0.6001:beta:~edition_value~~windows~~' + self.assertEqual(expected_uri, self.wfn_converter.convert_wfn_to_uri(wfn)) + + wfn = {'part': 'a', 'vendor': 'microsoft', 'product': 'internet_explorer', 'version': '8.0.6001', + 'update': 'beta', 'edition': 'edition_value', 'sw_edition': 'ANY', 'target_sw': 'windows', + 'target_hw': 'ANY', 'other': 'ANY', 'language': 'language'} + expected_uri = 'cpe:/a:microsoft:internet_explorer:8.0.6001:beta:language~edition_value~~windows~~' + self.assertEqual(expected_uri, self.wfn_converter.convert_wfn_to_uri(wfn)) + + wfn = {'part': 'a', 'vendor': 'microsoft', 'product': 'internet_explorer', 'version': '8.0.6001', + 'update': 'beta', 'edition': 'NA', 'sw_edition': 'NA', 'target_sw': 'windows', 'target_hw': 'NA', + 'other': 'NA', 'language': 'language'} + expected_uri = 'cpe:/a:microsoft:internet_explorer:8.0.6001:beta:language~-~-~windows~-~-' + self.assertEqual(expected_uri, self.wfn_converter.convert_wfn_to_uri(wfn)) + + def test_create_wfn_from_user_input(self): + user_input = {'other': ['ANY'], 'target_hw': ['ANY'], 'vendor': ['microsoft'], 'update': ['sp1'], + 'language': ['de'], 'sw_edition': ['ANY'], 'product': ['visual_foxpro'], 'target_sw': ['windows'], + 'version': ['9.0.30729.6161'], 'part': ['a'], + 'csrfmiddlewaretoken': ['G2Tg4v6Lo7pZ1bnIrz5b6X37jysLMTYH'], 'edition': ['ANY']} + + expected_wfn = {'part': 'a', 'vendor': 'microsoft', 'target_sw': 'windows', 'product': 'visual_foxpro', + 'target_hw': 'ANY', 'update': 'sp1', 'version': '9.0.30729.6161', 'sw_edition': 'ANY', + 'language': 'de', 'edition': 'ANY', 'other': 'ANY'} + wfn = self.wfn_converter.create_wfn_from_user_input(user_input) + self.test_utils.assertEqualKeys(expected_wfn, wfn) + self.test_utils.assertEqualValues(expected_wfn, wfn) + + def assert_wfn_docs_equal(self, expected_wfn_document, wfn): + self.assertIsNotNone(wfn) + self.test_utils.assertEqualKeys(expected_wfn_document, wfn) + self.test_utils.assertEqualValues(expected_wfn_document, wfn) + + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/user_authentication/ldap_client.py b/user_authentication/ldap_client.py new file mode 100644 index 0000000..10edc04 --- /dev/null +++ b/user_authentication/ldap_client.py @@ -0,0 +1,56 @@ +import ssl +import config +import traceback +import logger +from ldap3 import Server, Connection, Tls +from ldap3.core.exceptions import LDAPSocketOpenError, LDAPStartTLSError, LDAPPasswordIsMandatoryError + + +CONNECT_TIMEOUT = 15 + + +def get_user_from_ldap_dir(username, password): + user = None + connection = create_connection(password, username) + if connection: + try: + if connection.bind(): + user = connection.extend.standard.who_am_i() + connection.unbind() + except (LDAPPasswordIsMandatoryError, Exception): + log_traceback() + return user + + +def create_connection(password, username): + try: + connection = Connection(create_server(), get_bind_dn(username), password) + connection.open() + start_tls(connection) + return connection + except (LDAPSocketOpenError, LDAPStartTLSError, Exception): + log_traceback() + return False + + +def create_server(): + if config.is_ldap_tls_enabled(): + return Server(config.get_ldap_host(), port=config.get_ldap_port(), tls=create_tls_context(), connect_timeout=CONNECT_TIMEOUT) + return Server(config.get_ldap_host(), port=config.get_ldap_port(), connect_timeout=CONNECT_TIMEOUT) + + +def create_tls_context(): + return Tls(validate=ssl.CERT_REQUIRED, version=ssl.PROTOCOL_TLSv1_2, ca_certs_file=config.get_ldap_cacert_file_path()) + + +def get_bind_dn(username): + return 'cn='+username+','+config.get_ldap_base_dn() + + +def start_tls(connection): + if config.is_ldap_tls_enabled(): + connection.starttls() + + +def log_traceback(): + logger.error('LDAP - ' + str(traceback.format_exc())) \ No newline at end of file diff --git a/user_authentication/user.py b/user_authentication/user.py new file mode 100644 index 0000000..ff6cacb --- /dev/null +++ b/user_authentication/user.py @@ -0,0 +1,136 @@ +import os +import logger +from database import Database +from user_authentication import ldap_client +from user_authentication.user_dict import * +from utils import generate_password + +DUMMY_CREDENTIALS_FILE = 'dummy_user_credentials.txt' +USER_DB_COLLECTION = 'users' +USER_TYPES = namedtuple('User_Types', ['LOCAL', 'LDAP'])('local', 'ldap') + + +class User: + + def __init__(self): + self.db = Database() + self.username = None + self.user_type = None + + def insert_new_user(self, name, surname, username, pwd): + if not self.exists_in_db(username, pwd): + user_dict = create_user_dict(name, pwd, surname, username) + self.db.insert_document_in_collection(user_dict, USER_DB_COLLECTION) + else: + raise UserExistsException(username) + + def verify_user(self, username, pwd): + if self.exists_in_db(username, pwd): + self.set_user_info(username, USER_TYPES.LOCAL) + log_user_authenticated(username) + return True + elif is_ldap_user(pwd, username): + self.set_user_info(username, USER_TYPES.LDAP) + log_user_authenticated(username) + return True + log_invalid_user(username) + return False + + def set_user_info(self, username, user_type): + self.username = username + self.user_type = user_type + + def exists_in_db(self, username, pwd): + return self.db.exist_doc_in_collection(get_username_pwd_dict(pwd, username), USER_DB_COLLECTION) + + def exist_user_with_username(self, username): + return self.db.exist_doc_in_collection(get_username_dict(username), USER_DB_COLLECTION) + + def get_user_from_db(self, username, pwd): + return self.db.search_document_in_collection(get_username_pwd_dict(pwd, username), USER_DB_COLLECTION) + + def get_users(self): + return list(self.db.get_documents_from_collection(USER_DB_COLLECTION)) + + def delete_user(self, username): + self.db.delete_document_from_collection(get_username_dict(username), USER_DB_COLLECTION) + + def change_password(self, username, old_pwd, new_pwd): + if self.exists_in_db(username, old_pwd): + if new_pwd != '' or new_pwd is not None: + self.db.update_document_in_collection(get_username_dict(username), get_pwd_dict(new_pwd), USER_DB_COLLECTION) + return True + return False + + def modify_user(self, old_username, new_username, name, surname): + if old_username != new_username: + if not self.username_exists(new_username): + self.modify(old_username, new_username, name, surname) + else: + self.modify(old_username, new_username, name, surname) + + def modify(self, old_username, new_username, name, surname): + self.db.update_document_in_collection(get_username_dict(old_username), + get_name_surname_username_dict(name, new_username, surname), + USER_DB_COLLECTION) + + def username_exists(self, username): + return self.db.exist_doc_in_collection(get_username_dict(username), USER_DB_COLLECTION) + + def is_user_collection_empty(self): + return self.db.get_number_of_documents_in_collection(USER_DB_COLLECTION) == 0 + + def create_dummy_user(self): + username = generate_password() + password = generate_password() + self.insert_dummy_user_in_db(username, password) + print_credentials_on_terminal(username, password) + create_dummy_user_file(username, password) + + def insert_dummy_user_in_db(self, username, password): + self.insert_new_user('Dummy', 'User', username, password) + + +def is_ldap_user(pwd, username): + ldap_user = ldap_client.get_user_from_ldap_dir(username, pwd) + if ldap_user is not None: + return True + return False + + +def create_dummy_user_file(username, password): + f = open(get_dummy_user_file(), 'wb+') + f.write(bytes(username + '\n', 'UTF-8')) + f.write(bytes(password + '\n', 'UTF-8')) + f.close() + + +def get_dummy_user_file(): + return os.path.join(os.path.dirname(os.path.realpath(__file__)), DUMMY_CREDENTIALS_FILE) + + +def print_credentials_on_terminal(username, password): + print('################ Dummy User Credentials ################') + print('username: ' + str(username)) + print('password: ' + str(password)) + print('########################################################') + + +def log_user_authenticated(username): + logger.info('USER - ' + str(username) + ' authenticated successfully') + + +def log_invalid_user(username): + logger.error('USER - failed to authenticate user ' + str(username)) + + +class UserExistsException(Exception): + + def __init__(self, value): + self.user = value + + def __str__(self): + return repr(self.user) + + + diff --git a/user_authentication/user_dict.py b/user_authentication/user_dict.py new file mode 100644 index 0000000..926e697 --- /dev/null +++ b/user_authentication/user_dict.py @@ -0,0 +1,28 @@ +import hashlib +from collections import namedtuple + +DICT_KEYS = namedtuple('DICT_KEYS', ['NAME', 'SURNAME', 'USERNAME', 'PWD_HASH'])('name', 'surname', 'username', 'pwd_sha512') + + +def create_user_dict(name, pwd, surname, username): + return {DICT_KEYS.NAME: name, DICT_KEYS.SURNAME: surname, DICT_KEYS.USERNAME: username, DICT_KEYS.PWD_HASH: hash_(pwd)} + + +def get_username_pwd_dict(pwd, username): + return {DICT_KEYS.USERNAME: username, DICT_KEYS.PWD_HASH: hash_(pwd)} + + +def get_username_dict(username): + return {DICT_KEYS.USERNAME: username} + + +def get_pwd_dict(new_pwd): + return {DICT_KEYS.PWD_HASH: hash_(new_pwd)} + + +def get_name_surname_username_dict(name, new_username, surname): + return {DICT_KEYS.NAME: name, DICT_KEYS.SURNAME: surname, DICT_KEYS.USERNAME: new_username} + + +def hash_(pwd): + return hashlib.sha512(str.encode(pwd)).hexdigest() diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..f541a89 --- /dev/null +++ b/utils.py @@ -0,0 +1,30 @@ +import zipfile +import random +from logger import logger + + +def unzip_file_from_server_response(server_response, output_dir): + try: + zf = zipfile.ZipFile(server_response, 'r') + filename = zf.namelist().pop() + zf.extractall(output_dir) + zf.close() + return filename + except: + logger.error('unable to unzip server response. It is not a zip file') + return None + + +def generate_password(): + pwd = '' + characters = get_password_chars() + for i in range(random.randrange(8, 12)): + pwd += characters[random.randrange(len(characters))] + return pwd + + +def get_password_chars(): + alphabet = 'abcdefghijklmnopqrstuvwxyx' + return alphabet + alphabet.upper() + '0123456789' + '@_-()[]?=' + + diff --git a/wfn/encoding.py b/wfn/encoding.py new file mode 100644 index 0000000..7ed1191 --- /dev/null +++ b/wfn/encoding.py @@ -0,0 +1,39 @@ +characters_map = {'%21': '!', '%22': '\"', '%23': '#', '%24': '$', '%25': '%', '%26': '&', '%27': '\'', + '%28': '(', '%29': ')', '%2a': '*', '%2b': '+', '%2c': ',', '%2f': '/', '%3a': ':', '%3b': ';', + '%3c': '<', '%3d': '=', '%3e': '>', '%3f': '?', '%40': '@', '%5b': '[', '%5c': '\\', '%5d': ']', + '%5e': '^', '%60': '`', '%7b': '{', '%7c': '|', '%7d': '}', '%7e': '~'} + + +class Encoder: + + @staticmethod + def encode_escaped_double_points(string): + return string.replace('\\:', '%3a') + + @staticmethod + def encode_escaped_tildes(string): + return string.replace('\\~', '%7e') + + @staticmethod + def encode_non_alphanumeric_characters(string): + string = string.replace(characters_map.get('%25'), '%25') + for encoded_char in characters_map.keys(): + if characters_map.get(encoded_char) != '%': + string = string.replace(characters_map.get(encoded_char), encoded_char) + return string + + +class Decoder: + + @staticmethod + def decode_non_alphanumeric_characters(string): + string = decode_special_chars(string) + for encoded_char in characters_map.keys(): + string = string.replace(encoded_char, characters_map.get(encoded_char)) + return string + + +def decode_special_chars(string): + string = string.replace('%01', '?') + string = string.replace('%02', '*') + return string diff --git a/wfn/wfn_comparator.py b/wfn/wfn_comparator.py new file mode 100644 index 0000000..f9a2bc6 --- /dev/null +++ b/wfn/wfn_comparator.py @@ -0,0 +1,79 @@ +weights = {'vendor': 2, + 'product': 2, + 'version': 1.5, + 'update': 1, + 'edition': 1, + 'language': 0.5, + 'sw_edition': 0.5, + 'target_sw': 0.5, + 'target_hw': 0.5, + 'other': 0.5} + + +def compare_wfn(wfn_a, wfn_b): + coincidence = 0 + not_matches = [] + for key in weights.keys(): + if values_equal(wfn_a.get(key), wfn_b.get(key), key): + coincidence += weights.get(key) * 10 + else: + not_matches.append(key) + return {'coincidence_rate': coincidence, 'not_matches': not_matches} + + +def versions_equal(version_a, version_b): + version_elements_a, version_elements_b = get_version_elements(version_a, version_b) + if ('*' not in version_elements_a) and ('*' not in version_elements_b): + return values_equal(version_a, version_b, '') + return version_elements_equal(version_elements_a, version_elements_b) + + +def values_equal(val_a, val_b, key): + if key == 'version': + return versions_equal(val_a, val_b) + return (val_a == val_b) or (are_values_any(val_a, val_b)) + + +def get_version_elements(version_a, version_b): + return version_a.split('.'), version_b.split('.') + + +def version_elements_equal(version_elements_a, version_elements_b): + version_elements_a, version_elements_b = swap(version_elements_a, version_elements_b) + asterisk_index = version_elements_a.index('*') + if asterisk_index > len(version_elements_b): + return False + for i in range(asterisk_index): + if version_elements_a[i] != version_elements_b[i]: + return False + if asterisk_index < len(version_elements_a)-1: + if len(version_elements_b) != len(version_elements_a): + return False + for j in range(asterisk_index+1, len(version_elements_a)): + if version_elements_a[j] != version_elements_b[j]: + return False + return True + + +def swap(version_elements_a, version_elements_b): + if '*' in version_elements_a and '*' in version_elements_b: + index_a = version_elements_a.index('*') + index_b = version_elements_b.index('*') + if index_a <= index_b: + return version_elements_a, version_elements_b + return version_elements_b, version_elements_a + elif '*' in version_elements_a: + return version_elements_a, version_elements_b + return version_elements_b, version_elements_a + + +def are_values_any(val_a, val_b): + return (is_value_any(val_a) and not is_value_na(val_b)) or (is_value_any(val_b) and not is_value_na(val_a)) + + +def is_value_any(val): + return val == 'ANY' or val == '*' + + +def is_value_na(val): + return val == 'NA' or val == '-' diff --git a/wfn/wfn_converter.py b/wfn/wfn_converter.py new file mode 100644 index 0000000..05886e7 --- /dev/null +++ b/wfn/wfn_converter.py @@ -0,0 +1,164 @@ +from wfn.encoding import Decoder, Encoder + + +class WFNConverter: + + def __init__(self): + self.wfn_doc = {} + self.wfn_keys = ['part', 'vendor', 'product', 'version', 'update', 'edition', 'language', 'sw_edition', + 'target_sw', 'target_hw', 'other'] + self.wfn_keys_edition_special_case = ['part', 'vendor', 'product', 'version', 'update', 'language', 'edition', + 'sw_edition', 'target_sw', 'target_hw', 'other'] + + def convert_cpe_uri_to_wfn(self, cpe_uri): + self.set_wfn_default_values() + cpe_uri = self.encode_cpe_uri(cpe_uri) + wfn_values = self.get_wfn_values_from_cpe_uri(cpe_uri) + self.set_wfn_values(wfn_values) + return self.wfn_doc + + def set_wfn_default_values(self): + self.wfn_doc = {'part': 'ANY', 'vendor': 'ANY', 'product': 'ANY', 'version': 'ANY', 'update': 'ANY', + 'edition': 'ANY', 'language': 'ANY', 'sw_edition': 'ANY', 'target_sw': 'ANY', + 'target_hw': 'ANY', 'other': 'ANY'} + + @staticmethod + def encode_cpe_uri(cpe_uri): + cpe_uri = Encoder.encode_escaped_double_points(cpe_uri) + cpe_uri = Encoder.encode_escaped_tildes(cpe_uri) + return cpe_uri + + @staticmethod + def get_wfn_values_from_cpe_uri(cpe_uri): + wfn_first_part, wfn_second_part = WFNConverter.get_wfn_parts(cpe_uri) + wfn_values = WFNConverter.merge_wfn_parts(wfn_first_part, wfn_second_part) + WFNConverter.clean_values(wfn_values) + return wfn_values + + @staticmethod + def clean_values(values): + values.remove('cpe') # discard 'cpe' value + values[0] = WFNConverter.remove_slash_from_value(values[0]) + + @staticmethod + def get_wfn_parts(cpe_uri): + first_part = cpe_uri.split(':') + second_part = first_part[-1].split('~') + return first_part, second_part + + @staticmethod + def merge_wfn_parts(wfn_first_part, wfn_second_part): + if len(wfn_second_part) > 1: + lang = WFNConverter.get_lang_from_wfn_first_part(wfn_first_part) + del wfn_first_part[-1] # remove value of second part + wfn_first_part.append(lang) + del wfn_second_part[0] # remove value of first part + wfn_first_part.extend(wfn_second_part) + return wfn_first_part + + @staticmethod + def get_lang_from_wfn_first_part(first_part_values): + return first_part_values[-1].split('~')[0] + + def set_wfn_values(self, wfn_values): + wfn_keys_index = 0 + wfn_keys = self.get_wfn_keys(wfn_values) + for wfn_value in wfn_values: + wfn_value = Decoder.decode_non_alphanumeric_characters(wfn_value) + wfn_key = wfn_keys[wfn_keys_index] + self.set_wfn_value(wfn_key, wfn_value) + wfn_keys_index += 1 + + def get_wfn_keys(self, wfn_values): + if len(wfn_values) > 7: + return self.wfn_keys_edition_special_case + return self.wfn_keys + + def set_wfn_value(self, key, value): + if not self.is_value_any(value): + if value == '-': + self.set_wfn_value(key, 'NA') + else: + self.wfn_doc.__setitem__(key, value) + + @staticmethod + def is_value_any(value): + return value == '' or value == '*' or value == 'ANY' + + @staticmethod + def remove_slash_from_value(wfn_value): + return wfn_value.replace('/', '') + + def get_uri_binding_version(self, uri_binding): + return self.convert_cpe_uri_to_wfn(uri_binding).get('version') + + def get_uri_binding_target_sw(self, uri_binding): + return self.convert_cpe_uri_to_wfn(uri_binding).get('target_sw') + + def convert_wfn_to_uri(self, wfn): + uri = 'cpe:/' + special_case = self.is_wfn_special_case(wfn) + if not special_case: + uri_first_part_attributes = self.get_uri_first_part_attributes(wfn) + uri += self.concat_uri_attributes(':', uri_first_part_attributes) + else: + uri_first_part_attributes = self.get_uri_first_part_attributes(wfn, True) + uri += self.concat_uri_attributes(':', uri_first_part_attributes, True) + uri = self.concatenate_uri_second_part_attributes(uri, wfn) + return uri + + def get_uri_first_part_attributes(self, wfn, edition_special_case=False): + uri_first_part_attributes = [] + range_limit = 7 + wfn_keys = self.wfn_keys + if edition_special_case: + range_limit = 6 + wfn_keys = self.wfn_keys_edition_special_case + for i in range(range_limit): + uri_first_part_attributes.append(wfn.get(wfn_keys[i])) + return uri_first_part_attributes + + def is_wfn_special_case(self, wfn): + special_case = False + for i in range(7, 11): + attribute = wfn.get(self.wfn_keys[i]) + if not self.is_value_any(attribute): + return True + return special_case + + @staticmethod + def concat_uri_attributes(splitter_char, attributes, edition_special_case=False): + uri = '' + if not edition_special_case: + while WFNConverter.is_value_any(attributes[-1]): + attributes.pop() + for attribute in attributes: + uri = WFNConverter.concat_uri_attribute(uri, attribute, splitter_char) + return uri[:-1] + + def concatenate_uri_second_part_attributes(self, uri, wfn): + uri += '~' + for i in range(6, 11): + uri_attribute = wfn.get(self.wfn_keys_edition_special_case[i]) + uri = self.concat_uri_attribute(uri, uri_attribute, '~') + return uri[:-1] + + @staticmethod + def concat_uri_attribute(uri, attribute, splitter_char): + attribute = Encoder.encode_non_alphanumeric_characters(attribute) + if attribute == 'NA': + uri += '-' + splitter_char + elif attribute != 'ANY': + uri += attribute + splitter_char + else: + uri += splitter_char + return uri + + def create_wfn_from_user_input(self, user_input): + self.set_wfn_default_values() + for key in self.wfn_keys: + value = dict(user_input).get(key) + if value is not None: + self.set_wfn_value(key, value[0]) + return self.wfn_doc +