diff --git a/CHANGELOG.md b/CHANGELOG.md index b010bc8..640ec77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,26 @@ # VINCE Changelog +## Version 2.0.5 2023-01-04 + +* Update to CVE2.1 Services Publish using CVE5 JSON +* More Async functions for vendor status views +* Added more common libraries to lib/vince/utils +* Added a mute_lib.py to support mute a Case for a user in automated way +* Fixed a number of small bugs in max length in FORM submissions and S3 sensitive filenames + +## Version 2.0.4: 2022-12-20 + +* Added Filter to CaseView in VinceComm +* Addition of more Async functions for non-interactive queries +* Fixing of slow performance on allvendors view to use Django Aggregate and Filter/Q functions +* Friendly errors and fixes for logging to add IP address of remote client + + ## Version 2.0.3: 2022-12-14 * Major upgrade to Django 3.2 LTS target end byt 2024. Fixes related to Django upgrade in all libraries. -* Aded new QuerySet Paging library for performance extend chain with chainqs for QuerySet +* Aded new QuerySet Paging library for performance extend chain with chainqs for QuerySet * Asynchronous calls for most vinny/views via JSON through asyncLoad class * Provide API Views 404 with JSON generic error * Allow Session or API Token authentication to support API access from browser diff --git a/bigvince/settings_.py b/bigvince/settings_.py index 845db1a..acb61f6 100644 --- a/bigvince/settings_.py +++ b/bigvince/settings_.py @@ -56,7 +56,7 @@ ROOT_DIR = environ.Path(__file__) - 3 # any change that requires database migrations is a minor release -VERSION = "2.0.3" +VERSION = "2.0.5" # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/ diff --git a/lib/vince/mutelib.py b/lib/vince/mutelib.py new file mode 100644 index 0000000..9c11f2d --- /dev/null +++ b/lib/vince/mutelib.py @@ -0,0 +1,39 @@ +from django.contrib.auth.models import User +from vinny.views import VinceProfile as vp + + + +def mute_user(useremail,case_id,interactive=False): + """ Mute case for a user with `useremail` identified for a `case_id` + on success it return 0 (no need to update) or 1. If the user is not + found or user has nor profile, it returns -ve number repsectively. + You should use this with try/except block for web/API usage + """ + q = User.objects.filter(username=useremail).using('vincecomm').first() + l = vp.objects.filter(user=q).first() + if not q: + if interactive: + print(f"User {useremail} not found") + return -1 + if not l: + if interactive: + print(f"User {useremail} Profile not found") + return -2 + d = q.vinceprofile.settings.copy() + if 'muted_cases' in d: + if case_id in d['muted_cases']: + if interactive: + print(f"Case id {case_id} already muted for {useremail}") + print(d) + return 0 + else: + d['muted_cases'] += [case_id] + else: + d['muted_cases'] = [case_id] + l._set_settings(d) + l.save() + if interactive: + print("Updated profile settings are ") + print(l._get_settings()) + return 1 + diff --git a/lib/vince/utils.py b/lib/vince/utils.py new file mode 100644 index 0000000..8076f0b --- /dev/null +++ b/lib/vince/utils.py @@ -0,0 +1,60 @@ +import inspect +import pathlib +import mimetypes +import uuid +import re +#Utilities for VINCE to use that are generic + +def get_ip(request): + """ GET IP address of a request object and find it using simple + method of the first X-Forwarded-For header IP from proxy/web server + or the REMOTE_ADDR environment setup by the appserver. Returns a + string not an IP validated item/object. + """ + try: + if request.META.get('HTTP_X_FORWARDED_FOR'): + return request.META.get('HTTP_X_FORWARDED_FOR').split(',')[0] + elif request.META.get('REMOTE_ADDR'): + return request.META.get('REMOTE_ADDR') + else: + return "Unknown" + except Exception as e: + return f"IP lookup Exception {e}" + return "Unknown" + + +def deepGet(obj,idir): + """ Given an object of any kind find if it is a dictionary + or a list or an abstract object or instance of a class + that has a burried element. + """ + x = obj + for s in idir.split("."): + if not x: + return None + if isinstance(x,dict) and s in x: + x = x[s] + elif isinstance(x,list) and s.isdigit() and int(s) < len(x): + x = x[int(s)] + elif hasattr(x,s): + x = getattr(x,s) + if callable(x) and not inspect.isclass(x): + x = x() + else: + return None + return x + +def safe_filename(filename,file_uuid=str(uuid.uuid4()),mime_type="application/octet-stream"): + filename = filename.replace("\r"," ").replace("\n"," ").strip() + if re.search(r'[^\x00-\x7F]+',filename): + #non-ascii filenames use uuid and extension + if file_uuid == None: + file_uuid = uuid.uuid4() + file_extension = "".join(pathlib.Path(filename).suffixes) + if file_extension: + filename = file_uuid + file_extension + elif mimetypes.guess_extension(mime_type): + filename = file_uuid + mimetypes.guess_extension(mime_type) + else: + filename = file_uuid + return filename diff --git a/vince/forms.py b/vince/forms.py index cd5481e..234c1c0 100644 --- a/vince/forms.py +++ b/vince/forms.py @@ -2344,7 +2344,7 @@ class CVEAffectedProductForm(forms.ModelForm): widget = forms.Select(attrs={'class': 'form-control'}), label=_('Version Affected'), required=False, - choices=([('None', None), ('<', '< (affects X versions prior to n)'), ('<=', '<= (affects X versions up to n)'), ('=', '= (affects n)'), ('>', '> (affects X versions above n)'), ('>=', '>= (affects X versions n and above)')]) + choices=([('None', None), ('lessThan', '< (affects X versions prior to n)'), ('lessThanOrEqual', '<= (affects X versions up to n)')]) ) class Meta: @@ -2358,13 +2358,13 @@ class CVEReferencesForm(forms.Form): widget = forms.Select(attrs={'class': 'form-control'}), label=_('Reference Source'), required=True, - choices=([('URL', 'URL'),('CERT-VN', 'CERT-VN'), ('MISC', 'MISC'), ('CONFIRM', 'CONFIRM')]) + choices=([('URL', 'URL')]) ) reference = forms.URLField( label=_('Reference'), widget = forms.TextInput(attrs={'placeholder': 'e.g., https://dhs.gov.'}), - help_text = 'Please provide reference URL.', + help_text = 'Please provide reference URL. Do not add kb.cert.org reference. It will be automatically generated by the VU#', max_length=500 ) diff --git a/vince/models.py b/vince/models.py index 08e5d05..475eeef 100644 --- a/vince/models.py +++ b/vince/models.py @@ -48,6 +48,7 @@ #Django 3 and up from django.db.models import JSONField import io +from lib.vince import utils as vinceutils logger = logging.getLogger(__name__) @@ -1268,7 +1269,8 @@ def __str__(self): return '%s' % self.filename def _get_access_url(self): - url = self.file.storage.url(self.file.name, parameters={'ResponseContentDisposition': f'attachment; filename="{self.filename}"'}) + filename = vinceutils.safe_filename(self.filename,str(self.uuid),self.mime_type) + url = self.file.storage.url(self.file.name, parameters={'ResponseContentDisposition': f'attachment; filename="{filename}"'}) return url access_url = property(_get_access_url) @@ -1680,8 +1682,6 @@ class VulNoteRevision(BaseRevisionMixin, models.Model): search_vector = SearchVectorField(null=True) def __str__(self): - logger.debug("in revision") - logger.debug(self.content) if self.revision_number: return "%s (%d)" % (self.title, self.revision_number) else: @@ -3821,7 +3821,6 @@ def complete(self): else: return False if self.cve_name and self.date_public and len(refs) and len(cwes): - logger.debug(len(self.references)) return True else: return False @@ -3836,19 +3835,19 @@ class CVEAffectedProduct(models.Model): max_length=200) version_name = models.CharField( - _('Affected Version'), + _('Version Range End'), blank=True, null=True, max_length=100) version_affected = models.CharField( - _('Version Affected'), + _('Version Range Type'), blank=True, null=True, max_length=10) version_value = models.CharField( - _('Affected Version Value'), + _('Affected Version or Start'), max_length=100) organization = models.CharField( diff --git a/vince/static/vince/css/overrides.css b/vince/static/vince/css/overrides.css index 7001e1c..c8ac0c8 100644 --- a/vince/static/vince/css/overrides.css +++ b/vince/static/vince/css/overrides.css @@ -24,11 +24,11 @@ height: auto !important; } #offCanvas ul.vertical.menu > li.menu-close { - position: fixed; - right: 15%; - top: 0px; - border: none; - padding-top: 0px; + position: fixed; + right: 15%; + top: 0px; + border: none; + padding-top: 0px; } #header { height: auto !important; @@ -48,3 +48,74 @@ top: 0%; } } +nav.cdown { + display:inline-block; +} + +nav.cdown ul { + background: #ff8c00; + list-style: none; + margin: 0; + padding-left: 0; +} + +nav.cdown li { + color: #fff; + background: #ff8c00; + display: block; + float: left; + font-size: 0.8rem; + padding: 0.3rem 0.4rem 0.1rem 0.4rem; + position: relative; + text-decoration: none; + transition-duration: 0.5s; +} + +nav.cdown li a { + color: #fff; +} + +nav.cdown li:hover { + background: #dc3545; + cursor: pointer; +} + +nav.cdown ul li ul { + background: #ffa500; + visibility: hidden; + opacity: 0; + min-width: 9rem; + position: absolute; + transition: all 0.5s ease; + margin-top: 1rem; + right: 0; + display: none; + top: 0.55rem; +} + +nav.cdown ul li:hover > ul, +nav.cdown ul li:focus-within > ul, +nav.cdown ul li ul:hover { + visibility: visible; + opacity: 1; + display: block; +} + +nav.cdown ul li ul li { + clear: both; + width: 100%; +} +nav.cdown .fa-check { + font-size: 0.7rem; + opacity: 0; +} +nav.cdown .all .fa-check { + opacity: 1; +} +nav.cdown li.affected { + background-color: #990033; +} +nav.cdown li.not_affected { + background-color: #3adb76; +} + diff --git a/vince/static/vince/js/addvuls.js b/vince/static/vince/js/addvuls.js index 51e77d3..98ce5ef 100644 --- a/vince/static/vince/js/addvuls.js +++ b/vince/static/vince/js/addvuls.js @@ -129,6 +129,11 @@ function del_tag(taggle, tag, modal){ $(document).ready(function() { + if($('#largemodal').length < 1) { + $('body').prepend('
'); + } + let _ = new Foundation.Reveal($('#largemodal')); var modal = $("#largemodal"); var vul_tags = []; diff --git a/vince/static/vince/js/case.js b/vince/static/vince/js/case.js index ef49993..cd60b48 100644 --- a/vince/static/vince/js/case.js +++ b/vince/static/vince/js/case.js @@ -1,38 +1,43 @@ /*######################################################################### -# VINCE -# -# Copyright 2022 Carnegie Mellon University. -# -# NO WARRANTY. THIS CARNEGIE MELLON UNIVERSITY AND SOFTWARE ENGINEERING -# INSTITUTE MATERIAL IS FURNISHED ON AN "AS-IS" BASIS. CARNEGIE MELLON -# UNIVERSITY MAKES NO WARRANTIES OF ANY KIND, EITHER EXPRESSED OR IMPLIED, -# AS TO ANY MATTER INCLUDING, BUT NOT LIMITED TO, WARRANTY OF FITNESS FOR -# PURPOSE OR MERCHANTABILITY, EXCLUSIVITY, OR RESULTS OBTAINED FROM USE OF THE -# MATERIAL. CARNEGIE MELLON UNIVERSITY DOES NOT MAKE ANY WARRANTY OF ANY KIND -# WITH RESPECT TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT. -# -# Released under a MIT (SEI)-style license, please see license.txt or contact -# permission@sei.cmu.edu for full terms. -# -# [DISTRIBUTION STATEMENT A] This material has been approved for public -# release and unlimited distribution. Please see Copyright notice for non-US -# Government use and distribution. -# -# Carnegie Mellon®, CERT® and CERT Coordination Center® are registered in the -# U.S. Patent and Trademark Office by Carnegie Mellon University. -# -# This Software includes and/or makes use of Third-Party Software each subject -# to its own license. -# -# DM21-1126 -######################################################################## + # VINCE + # + # Copyright 2022 Carnegie Mellon University. + # + # NO WARRANTY. THIS CARNEGIE MELLON UNIVERSITY AND SOFTWARE ENGINEERING + # INSTITUTE MATERIAL IS FURNISHED ON AN "AS-IS" BASIS. CARNEGIE MELLON + # UNIVERSITY MAKES NO WARRANTIES OF ANY KIND, EITHER EXPRESSED OR IMPLIED, + # AS TO ANY MATTER INCLUDING, BUT NOT LIMITED TO, WARRANTY OF FITNESS FOR + # PURPOSE OR MERCHANTABILITY, EXCLUSIVITY, OR RESULTS OBTAINED FROM USE OF THE + # MATERIAL. CARNEGIE MELLON UNIVERSITY DOES NOT MAKE ANY WARRANTY OF ANY KIND + # WITH RESPECT TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT. + # + # Released under a MIT (SEI)-style license, please see license.txt or contact + # permission@sei.cmu.edu for full terms. + # + # [DISTRIBUTION STATEMENT A] This material has been approved for public + # release and unlimited distribution. Please see Copyright notice for non-US + # Government use and distribution. + # + # Carnegie Mellon®, CERT® and CERT Coordination Center® are registered in the + # U.S. Patent and Trademark Office by Carnegie Mellon University. + # + # This Software includes and/or makes use of Third-Party Software each subject + # to its own license. + # + # DM21-1126 + ######################################################################## */ +function alertmodal(modal,msg) { + modal.html("

" + + msg + "

") + .foundation('open'); +} function permissionDenied(modal) { - - modal.html("

Error: You are not permitted to perform this action

").foundation('open'); - + alertmodal(modal,"Error: You are not permitted to perform this action"); } function init_modal_markdown() { @@ -85,10 +90,10 @@ function reloadVendorStats(case_id) { function reloadVendors(case_id, tablet) { tablet.replaceData(); /*$.ajax({ - url: "/vince/ajax_calls/case/vendors/"+case_id+"/", - success: function(data) { + url: "/vince/ajax_calls/case/vendors/"+case_id+"/", + success: function(data) { - }});*/ + }});*/ reloadVendorStats(case_id); } @@ -161,7 +166,7 @@ function searchComms(e) { error: function() { lockunlock(false,'div.mainbody,div.vtmainbody','#timeline'); console.log(arguments); - alert("Search failed or canceled! See console log for details."); + alert("Search failed or canceled! See console log for details."); }, complete: function() { /* Just safety net */ @@ -200,7 +205,7 @@ function searchTasks(e, tablet) { error: function() { console.log(arguments); lockunlock(false,'div.mainbody,div.vtmainbody','#case_tasks'); - alert("Search failed or canceled! See console log for details."); + alert("Search failed or canceled! See console log for details."); }, complete: function() { /* Just safety net */ @@ -247,12 +252,12 @@ function auto(data, taggle, tag_url, modal) { } $(document).ready(function() { - $('a').each(function () { + $('a').each(function () { $(this).qtip({ content: $(this).attr("title"), style: {classes: 'qtip-youtube'} }) - }); + }); var input = document.getElementById("id_keyword"); if (input) { @@ -458,6 +463,7 @@ $(document).ready(function() { var largemodal = $("#largemodal"); $(document).on("submit", '#addvendorform', function(event) { + /* the jquery autocomplete should use UUID or PKIDs*/ event.preventDefault(); var reload = $(this).attr("reload"); var vendors = []; @@ -494,7 +500,7 @@ $(document).ready(function() { }) .done(function() { addmodal.foundation('close'); - }); + }); }); @@ -645,20 +651,141 @@ $(document).ready(function() { } }); }); + async function notify_all() { + let i = 1; + let hm = get_modal(); + hm.append("

Start all-vendor Notifications

") + hm.append("

Do not hit Escape or click outside this window!

"); + hm.append("

See Javascript console log for any failures

"); + let caseid = $('.addvulmodal').attr('caseid'); + if(!caseid) { + hm.append("Error no CaseID found"); + return false; + } + + window.allvendors = []; + var max = 2; + hm.append("
Fetching vendors list by Page " + + "[0] of " + + "2
"); + while (i <= max) { + url = 'https://vince.cert.org/vince/ajax_calls/case/vendors/'+ + caseid+'/?page='+String(i); + hm.find('.cpage').html(String(i)); + await $.get(url,function(d) { + if(d.last_page) + max = parseInt(d.last_page); + hm.find('.tpage').html(max); + if(d.data) + window.allvendors = window.allvendors.concat(d.data); + }); + i++; + } + hm.append("

Initiating contact for vendor # " + + "[0] of " + + window.allvendors.length + " Vendors in " + + "this Case, please wait

"); + hm.append("
"); + finish_modal(hm); + } - $(document).on("click", '#approveall', function(event) { - event.preventDefault(); - $.ajax({ - url: $(this).attr("href"), - type: "GET", - success: function(data) { - addmodal.html(data).foundation('open'); - }, - error: function(xhr, status) { - permissionDenied(addmodal); - } - }); - }); + + async function approve_all_vendors() { + let hm = get_modal(); + hm.append("

Start all-vendor Approval

"); + hm.append("

Do not close this window

"); + hm.append("

See Javascript console log for any failures

"); + let caseid = $('.addvulmodal').attr('caseid'); + if(!caseid) { + hm.append("Error no CaseID found"); + finish_modal(hm); + return false; + } + window.allvendors = []; + var max = 2; + let url = "https://vince.cert.org/vince/ajax_calls/case/vendors/"+ + caseid + "/?page=1&filters%5B0%5D%5Bfield%5D=reqapproval" + + "&filters%5B0%5D%5Btype%5D=%3D&filters%5B0%5D%5Bvalue%5D=true"; + hm.append("