Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Product Changes | PDF Download Button #104

Merged
merged 10 commits into from
May 8, 2023

Conversation

z-priest
Copy link
Contributor

This PR contains the working prototype implementation of the following features:

Issue #70

  • vendor-contact Product table - the ability to add/edit/delete product names and their respective critical infrastructure sectors from within a vendor contact
  • Case vendor drop down when adding affected products to case CVE - Only vendors attached to the case can have affected products in a case CVE
  • Auto-suggest product name based on selected vendor when adding affected products to case CVE - allowing to click and auto-fill the product name from the database or enter in new text to add a new product to the vendor
  • Banner above CVE displaying when there is unpublished CVE information (for example: new or changed vendors and products) - banner disappears after publishing or republishing the CVE
  • Additional notes have been added to the README in the localstackdev directory

Issue #67

  • PDF Button for Vul Notes - With accompanying headers/footers and a watermark on the front page

@z-priest z-priest changed the title Pr 70 Product Changes | PDF Download Button Apr 13, 2023
@sei-vsarvepalli
Copy link
Contributor

@z-priest

Just had a little time to take a quick look at the code itself before I write some tests.

  1. How is the vince.models.Sector managed? There seems to be no API calls for managing Sector I can see.
+    re_path(r'^ajax_calls/prods/(?P<org_id>.*)/$', views.autocomplete_prods),
+    re_path(r'^contact/add/product/(?P<pk>[0-9]+)/$', views.AddProductToContact.as_view(), name='addproduct'),
+    re_path(r'^contact/edit/product/(?P<pk>[0-9]+)/$', views.EditProduct.as_view(), name='editproduct'),
+    re_path('contact/remove/product/(?P<pk>[0-9]+)/(?P<product>[0-9]+)/$', views.RemoveProductFromContact.as_view(), name='rmproduct'),
  1. The autocomplete_prods uses a request.GET['term'] to find the relevant along with an org_id but when term is empty the org_id seems to be dropped? Can you check to see if it actually makes sense.

  2. Looks like you can do all of these under one ajax_call for all DML operations on Product. Combine them all to Asynch calls and avoid server requests (preferred but not required)

  3. On logging. It will be better to log also the method that is being called

logger.debug(self.request.POST)

should be more like

logger.debug(f"{self.__class__.__name__} post: {self.request.POST}")

vince/views.py Outdated
else:
prods = list(VendorProduct.objects.all().values_list('name', flat=True))

print('prods: ', prods)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't use debug prints use logger always.

@sei-vsarvepalli sei-vsarvepalli self-assigned this Apr 14, 2023
@sei-vsarvepalli sei-vsarvepalli added bug Something isn't working enhancement New feature or request labels Apr 14, 2023
@sei-vsarvepalli sei-vsarvepalli self-requested a review April 14, 2023 22:21
@mstrad
Copy link

mstrad commented Apr 17, 2023

@z-priest

Just had a little time to take a quick look at the code itself before I write some tests.

  1. How is the vince.models.Sector managed? There seems to be no API calls for managing Sector I can see.
+    re_path(r'^ajax_calls/prods/(?P<org_id>.*)/$', views.autocomplete_prods),
+    re_path(r'^contact/add/product/(?P<pk>[0-9]+)/$', views.AddProductToContact.as_view(), name='addproduct'),
+    re_path(r'^contact/edit/product/(?P<pk>[0-9]+)/$', views.EditProduct.as_view(), name='editproduct'),
+    re_path('contact/remove/product/(?P<pk>[0-9]+)/(?P<product>[0-9]+)/$', views.RemoveProductFromContact.as_view(), name='rmproduct'),
  1. The autocomplete_prods uses a request.GET['term'] to find the relevant along with an org_id but when term is empty the org_id seems to be dropped? Can you check to see if it actually makes sense.
  2. Looks like you can do all of these under one ajax_call for all DML operations on Product. Combine them all to Asynch calls and avoid server requests (preferred but not required)
  3. On logging. It will be better to log also the method that is being called
logger.debug(self.request.POST)

should be more like

logger.debug(f"{self.__class__.__name__} post: {self.request.POST}")

@sei-vsarvepalli Do you have an example in the code currently reflecting your suggestion to question (3)? So far the database calls for other views/models appear to be done similarly to what we've written. I worry about our timeline here in pushing these features into production by May 1st.

Another concern I have is data migration testing of these product features when they are put into production. How will currently saved products in existing CVE details be handled when our changes are implemented? Will you and your team be able to handle that?

Will follow up via email as well.

@sei-vsarvepalli
Copy link
Contributor

@z-priest
Just had a little time to take a quick look at the code itself before I write some tests.

  1. How is the vince.models.Sector managed? There seems to be no API calls for managing Sector I can see.
+    re_path(r'^ajax_calls/prods/(?P<org_id>.*)/$', views.autocomplete_prods),
+    re_path(r'^contact/add/product/(?P<pk>[0-9]+)/$', views.AddProductToContact.as_view(), name='addproduct'),
+    re_path(r'^contact/edit/product/(?P<pk>[0-9]+)/$', views.EditProduct.as_view(), name='editproduct'),
+    re_path('contact/remove/product/(?P<pk>[0-9]+)/(?P<product>[0-9]+)/$', views.RemoveProductFromContact.as_view(), name='rmproduct'),
  1. The autocomplete_prods uses a request.GET['term'] to find the relevant along with an org_id but when term is empty the org_id seems to be dropped? Can you check to see if it actually makes sense.
  2. Looks like you can do all of these under one ajax_call for all DML operations on Product. Combine them all to Asynch calls and avoid server requests (preferred but not required)
  3. On logging. It will be better to log also the method that is being called
logger.debug(self.request.POST)

should be more like

logger.debug(f"{self.__class__.__name__} post: {self.request.POST}")

@sei-vsarvepalli Do you have an example in the code currently reflecting your suggestion to question (3)? So far the database calls for other views/models appear to be done similarly to what we've written. I worry about our timeline here in pushing these features into production by May 1st.

Another concern I have is data migration testing of these product features when they are put into production. How will currently saved products in existing CVE details be handled when our changes are implemented? Will you and your team be able to handle that?

Will follow up via email as well.

For simplifying Products management

An example could be the the method vinny.views.casetracking In this code, a GET method is DQL to query for a record and it gets sent back in JSON format - you can use JavaScript to render/use it. A POST method is DML for Insert/Update. We can easily do a Delete DML with the POST as well. It is not exactly REST (with Django Serializers headache) but can be used access/manage data asynchronously. It will make separating presentation from data easier, no need to create/change forms.py but just HTML/Javascript based forms, that can be dynamically built on the client.

@user_passes_test(is_not_pending, login_url="vinny:login")

BTW - it is not in critical path for code implementation itself.

_On current data migration for Products _

I am assuming it will just be ignored from a quick view of your code. They will not be in the Products DB until, an edit and change of the Product. I have not tested it.

z-priest and others added 2 commits April 18, 2023 09:52
 - Use multi-line JavaScript comments for minify script
 - Remove URLs as hard-coded and use HTML data-attributes
 - Send dictionary from ajax_calls, avoid array/list to allow for extensions
 - Check to see if GET parameter is useable
@sei-vsarvepalli
Copy link
Contributor

sei-vsarvepalli commented Apr 19, 2023

Running some Tests against the database. Some UI issues found in click tester from Chrome. The Products already added cannot be modified. May consider "onfocus=blur" on those rows. Same can be applied for the CWE rows to reduce confusion on editing already added CWE's with new autocomplete entries.

@mstrad
Copy link

mstrad commented Apr 19, 2023

Running some Tests against the database. Some UI issues found in click tester from Chrome. The Products already added cannot be modified. May consider "onfocus=blur" on those rows. Same can be applied for the CWE rows to reduce confusion on editing already added CWE's with new autocomplete entries.

@sei-vsarvepalli Could you clarify the issue that you are seeing? On our end everytime we seek to edit a product we are able to on our end. And that should be allowed as far as we understand, because products entered in, a coordinator or even a vendor could make a mistake that needs to be fixed. Or affected products of a CVE may need to be updated.

@sei-vsarvepalli
Copy link
Contributor

Running some Tests against the database. Some UI issues found in click tester from Chrome. The Products already added cannot be modified. May consider "onfocus=blur" on those rows. Same can be applied for the CWE rows to reduce confusion on editing already added CWE's with new autocomplete entries.

@sei-vsarvepalli Could you clarify the issue that you are seeing? On our end everytime we seek to edit a product we are able to on our end. And that should be allowed as far as we understand, because products entered in, a coordinator or even a vendor could make a mistake that needs to be fixed. Or affected products of a CVE may need to be updated.

It looks like there are a few problems with org_auto function. May need to just rewrite this whole function. The item variable in the function is overloaded (string or jQuery object) and likely to be confused and cause issues. The preloaded values in a cve-product-$i when coming from a pre-existing value triggers org_auto(null, true) which activates the /* new form */ and will not enable autocomplete on these input items.

function org_auto(item, init) {
const regex = /[0-9]+/g;
let item_found = ''
let prod_input = ''
let orgid = ''
if(item){
orgid = $(item).val();
}
if (item && init == true) {
/* new row in form */
prod_input = $("#id_product-"+item[0].id.match(regex)[0]+"-cve_affected_product")
item_found = $("#id_product-"+item[0].id.match(regex)[0]+"-organization")
} else if (item && init == false){
/* came back to form after submit or after clearing an organization selection */
let row_id = $(item)[0].id.match(regex)[0]
prod_input = $("#id_product-"+item.match(regex)[0]+"-cve_affected_product")
item_found = $("#id_product-"+row_id+"-organization")
complete_prod(orgid, $(item)[0].id.match(regex)[0], prod_input, item_found)
} else {
/* new form */
prod_input = $('#id_product-0-cve_affected_product')
item_found = $('#id_product-0-organization')
}
const row_id = item_found[0].id.match(regex)[0]
prod_input.parent().append('<div id=newprod_indicator_'+row_id+' style="color:red;font-size:14px"></div>')
$(item_found).change(function() {
if (item_found.val()){
complete_prod(orgid, row_id, prod_input, item_found)
}
});
}

Will have to rewrite the item variable to jQuery selector and use forEach or each iterators for applying triggers to all the selected input items.

On HTML elements inline Javascript will fail on the AWS side as the CSP headers will block these too. Don't use onchange/onclick event handlers inside the HTML elements. These should all be triggered from document.ready() with bind or jQuery.on event handlers.

widget = forms.Select(attrs={'class': 'form-control', 'onChange': "change_org(this)"}),

I will update in a couple of days with new code.


sector = ArrayField( models.CharField( max_length = 50 ), blank = True, null = True )

uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
Copy link
Contributor

@sei-vsarvepalli sei-vsarvepalli Apr 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this have META - unique_together @mstrad @z-priest

    class Meta:
        unique_together = (('name', 'organization'),)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was not aware that unique_together was a thing, but that makes sense. In that case, would we need to change this to be UUIDv5? I believe UUIDv5 is deterministically calculated based on the combination of organization and name or does the UUID library somehow do this with UUIDv5?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to move to UUIDv5 - just the uniqueness can be enforced. Should the uniqueness be also be case insensitive? I can take care of the updates in the code.

for example "Vendor - newProduct" and "Vendor - NewProduct" should not be allowed - correct?

Verify from your ops to ensure that this is correct.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank You,

@mstrad I believe this would be correct.

Copy link

@mstrad mstrad Apr 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I believe that's correct

vince/views.py Outdated
organization=contact[0].vendor_name))

#check if vendorProduct exists, if not create it.
checkprod = VendorProduct.objects.filter(name = cd['cve_affected_product'])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this check for organization match as well? like below

checkprod = VendorProduct.objects.filter(name = cd['cve_affected_product'], organization=contact[0])

Preferably
checkprod = VendorProduct.objects.filter(name__iexact = cd['cve_affected_product'], organization=contact[0])

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Few bugs found when running further Test Cases against VendorProduct with multi vendors. A duplicate product name under two vendors causes Null-Property reference exception. On delete the cascade will also fail.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this check for organization match as well? like below

checkprod = VendorProduct.objects.filter(name = cd['cve_affected_product'], organization=contact[0])

Preferably checkprod = VendorProduct.objects.filter(name__iexact = cd['cve_affected_product'], organization=contact[0])

Yes, it should be!

Copy link
Contributor Author

@z-priest z-priest Apr 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Few bugs found when running further Test Cases against VendorProduct with multi vendors. A duplicate product name under two vendors causes Null-Property reference exception. On delete the cascade will also fail.

I guess it makes sense why it is failing, because there is a single VendorProduct table with reference to contact_fk and no uniqueness is built into that table property ['name']
I wonder how to solve this? Accessing UUID instead of name fields? Prepending the contact['name'] to a vendorproduct['name'] so ACME_Product1 when saving?

Then when displaying or modifying the name break the string on contact['name']_ ?

Or add a different another property as this above and keeping the ['name'] field?

Based on your feedback, I can spend some time working on a patch tomorrow for both this and what I can come up with for org_auto above.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@z-priest Don't bother more development, Part of the testCases suggest some patches. I will apply and run the test cases again and push updates later. This way we can avoid duplicate work as it is hard to push test outputs to you in our current setup.

 - Add unique_together for name+organization under VendorProduct
 - Check uniqueness of VendorProduct caseInsensitive
 - Convert org_auto and cwe_auto to Factory pattern
 - Merge <label> fields for multiple-row fields with disitinct id's to support accessibility
 - Bugs in CWE lookup
 - Added Cached lookup for CWE and Products with same filters.
@sei-vsarvepalli
Copy link
Contributor

After the 6e12387 push one problem remains, on change trigger to clear and repopulate autocomplete.

@sei-vsarvepalli
Copy link
Contributor

It looks like the problem is related to redirect issue in the server side vince/views.py test cases did not catch this!

return HttpResponseRedirect(reverse('vince:contact', args=orgid) + '#products')

Fix is

+       return HttpResponseRedirect(reverse('vince:contact', args=[orgid]) + '#products')
-        return HttpResponseRedirect(reverse('vince:contact', args=orgid) + '#products')

File "/var/app/current/vince/views.py", line 10533, in post return HttpResponseRedirect(reverse('vince:contact', args=orgid) + '#products') File "/var/app/venv/staging-LQM1lest/lib/python3.8/site-packages/django/urls/base.py", line 86, in reverse return resolver._reverse_with_prefix(view, prefix, *args, **kwargs) File "/var/app/venv/staging-LQM1lest/lib/python3.8/site-packages/django/urls/resolvers.py", line 698, in _reverse_with_prefix raise NoReverseMatch(msg)django.urls.exceptions.NoReverseMatch: Reverse for 'contact' with arguments '('2', '0')' not found. 1 pattern(s) tried: ['vince/contact/(?P[0-9]+)

@sei-vsarvepalli
Copy link
Contributor

This code is now running as v2.0.8 in AWS for testing. Chrome click testing in progress.

@sei-vsarvepalli
Copy link
Contributor

Current gaps observed by users, coordinators/operators and vendor-meeting feedback

  1. The product tree cannot be translated to CSAF JSON and will not be visible in the advisory - only in the CVE JSON for the CVE Program.
  2. Vendors will not have access to the Product and Version information and be able to provide Status appropriately or update any of the information.
  3. Only Affected status is saved by the Operator, if a Product or system is unaffected or unknown it is not captured/available
  4. The defaultStatus is not collected or not used from the CVE5 JSON schema
  5. No current way to link CSAF nested or CSAF related advisory links for a product status

Related urls from CVE Program and CSAF issues:
https://cveproject.github.io/cve-schema/schema/v5.0/docs/mindmap.html
CVEProject/cve-schema#215
oasis-tcs/csaf#584

@sei-vsarvepalli
Copy link
Contributor

Just came out yesterday from oss-security pointed out by @laurie-tyz - May consider this with 2.1.1 release candidate for Monday May 8th

https://www.djangoproject.com/weblog/2023/may/03/security-releases/

@sei-vsarvepalli sei-vsarvepalli added the security Affects security label May 3, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working enhancement New feature or request security Affects security
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants