From 640e1443fd5b4c3b58db9b2d39cf4d5380da396a Mon Sep 17 00:00:00 2001 From: davidpofo Date: Fri, 21 May 2021 09:12:37 -0400 Subject: [PATCH 01/28] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 43003dcf0..e3893255c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ v999 (May XX, 2021) **UI changes** * Can now edit a system componet's state and type in the detail page for a selected component. +* Can now create a component with a state and type with the `ElementForm` v0.9.3.5.3 (May 16, 2021) ------------------------- From 6fb52e862afc121a6e3c7f28ffc0786efeba8dec Mon Sep 17 00:00:00 2001 From: davidpofo Date: Fri, 21 May 2021 11:35:57 -0400 Subject: [PATCH 02/28] FISMA IMPACT LEVEL is now SECURITY SENSITIVITY LEVEL --- CHANGELOG.md | 1 + controls/enums/statements.py | 2 +- .../migrations/0052_auto_20210521_1422.py | 23 ++++++++++++++++ controls/models.py | 27 +++++++++---------- guidedmodules/views.py | 12 ++++----- siteapp/tests.py | 12 ++++----- siteapp/views.py | 13 +++++---- templates/project.html | 6 ++--- 8 files changed, 59 insertions(+), 37 deletions(-) create mode 100644 controls/migrations/0052_auto_20210521_1422.py diff --git a/CHANGELOG.md b/CHANGELOG.md index e3893255c..957696d8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ v999 (May XX, 2021) * Require components to have descriptions. * Adding component_state and component_type fields to an `Element` to contain a component's state and type. * Created a modal to allow an admin user to add security objectives confidentiality, integrity, and availability. +* Fisma impact level is now represented as Security Sensitivity level following OSCAL's schema. **UI changes** diff --git a/controls/enums/statements.py b/controls/enums/statements.py index 1887b0a26..da4f17c58 100644 --- a/controls/enums/statements.py +++ b/controls/enums/statements.py @@ -6,5 +6,5 @@ class StatementTypeEnum(BaseEnum): CONTROL_IMPLEMENTATION_PROTOTYPE = "control_implementation_prototype" ASSESSMENT_RESULT = "assessment_result" POAM = "POAM" - FISMA_IMPACT_LEVEL = "fisma_impact_level" + SECURITY_SENSITIVITY_LEVEL = "security_sensitivity_level" SECURITY_IMPACT_LEVEL = "security_impact_level" diff --git a/controls/migrations/0052_auto_20210521_1422.py b/controls/migrations/0052_auto_20210521_1422.py new file mode 100644 index 000000000..56fef7fe5 --- /dev/null +++ b/controls/migrations/0052_auto_20210521_1422.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2 on 2021-05-21 14:22 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('controls', '0051_auto_20210520_1213'), + ] + + operations = [ + migrations.AlterField( + model_name='historicalstatement', + name='statement_type', + field=models.CharField(blank=True, choices=[('CONTROL_IMPLEMENTATION', 'control_implementation'), ('CONTROL_IMPLEMENTATION_PROTOTYPE', 'control_implementation_prototype'), ('ASSESSMENT_RESULT', 'assessment_result'), ('POAM', 'POAM'), ('SECURITY_SENSITIVITY_LEVEL', 'security_sensitivity_level'), ('SECURITY_IMPACT_LEVEL', 'security_impact_level')], help_text='Statement type.', max_length=150, null=True), + ), + migrations.AlterField( + model_name='statement', + name='statement_type', + field=models.CharField(blank=True, choices=[('CONTROL_IMPLEMENTATION', 'control_implementation'), ('CONTROL_IMPLEMENTATION_PROTOTYPE', 'control_implementation_prototype'), ('ASSESSMENT_RESULT', 'assessment_result'), ('POAM', 'POAM'), ('SECURITY_SENSITIVITY_LEVEL', 'security_sensitivity_level'), ('SECURITY_IMPACT_LEVEL', 'security_impact_level')], help_text='Statement type.', max_length=150, null=True), + ), + ] diff --git a/controls/models.py b/controls/models.py index f00e435b7..1b08b6b6e 100644 --- a/controls/models.py +++ b/controls/models.py @@ -535,25 +535,24 @@ def remove_control(self, control_id): return control @transaction.atomic - def set_fisma_impact_level(self, fisma_impact_level): - """Assign FISMA impact level to system""" - # TODO: Fisma impact level is actually the security-sensitivity-level as defined in oscal ssp schema. - # Get or create the fisma_impact_level smt for system's root_element; should only have 1 statement - smt = Statement.objects.create(statement_type=StatementTypeEnum.FISMA_IMPACT_LEVEL.value, producer_element=self.root_element,consumer_element=self.root_element, body=fisma_impact_level) - return fisma_impact_level, smt + def set_security_sensitivity_level(self, security_sensitivity_level): + """Assign Security Sensitivty level to system""" + # Get or create the security_sensitivity_level smt for system's root_element; should only have 1 statement + smt = Statement.objects.create(statement_type=StatementTypeEnum.SECURITY_SENSITIVITY_LEVEL.value, producer_element=self.root_element, consumer_element=self.root_element, body=security_sensitivity_level) + return security_sensitivity_level, smt @property - def get_fisma_impact_level(self): - """Assign FISMA impact level to system""" + def get_security_sensitivity_level(self): + """Assign Security Sensitivty level to system""" - # Get or create the fisma_impact_level smt for system's root_element; should only have 1 statement - smt, created = Statement.objects.get_or_create(statement_type=StatementTypeEnum.FISMA_IMPACT_LEVEL.value, producer_element=self.root_element,consumer_element=self.root_element) - fisma_impact_level = smt.body - return fisma_impact_level + # Get or create the security_sensitivity_level smt for system's root_element; should only have 1 statement + smt, created = Statement.objects.get_or_create(statement_type=StatementTypeEnum.SECURITY_SENSITIVITY_LEVEL.value, producer_element=self.root_element, consumer_element=self.root_element) + security_sensitivity_level = smt.body + return security_sensitivity_level @transaction.atomic def set_security_impact_level(self, security_impact_level): - """Assign Security impact levels to system""" + """Assign one or more of the System Security impact levels (e.g. confidentiality, integrity, availability)""" security_objective_smt = self.root_element.statements_consumed.filter(statement_type=StatementTypeEnum.SECURITY_IMPACT_LEVEL.value) if security_objective_smt.exists(): @@ -565,7 +564,7 @@ def set_security_impact_level(self, security_impact_level): @property def get_security_impact_level(self): - """Assign Security impact levels to system""" + """Get one or more of the System Security impact levels (e.g. confidentiality, integrity, availability)""" # Get the security_impact_level smt for element; should only have 1 statement smt = Statement.objects.get(statement_type=StatementTypeEnum.SECURITY_IMPACT_LEVEL.value, producer_element=self.root_element, consumer_element=self.root_element) diff --git a/guidedmodules/views.py b/guidedmodules/views.py index a8e825b41..ec77c4e78 100644 --- a/guidedmodules/views.py +++ b/guidedmodules/views.py @@ -481,15 +481,15 @@ def redirect_to(): object={"object": "system", "id": system.id, "name": system.root_element.name}, user={"id": request.user.id, "username": request.user.username} ) - # Set fisma_impact_level statement + # Set security_sensitivity_level statement if baseline.lower() in ["low", "moderate", "high"]: - fisma_impact_level, smt = system.set_fisma_impact_level(baseline) - if fisma_impact_level == baseline.lower(): + security_sensitivity_level, smt = system.set_security_sensitivity_level(baseline) + if security_sensitivity_level == baseline.lower(): messages.add_message(request, messages.INFO, - f'I\'ve set the system FISMA impact level to "{fisma_impact_level}.') - # Log setting fisma_impact_level + f'I\'ve set the system FISMA impact level to "{security_sensitivity_level}.') + # Log setting security_sensitivity_level logger.info( - event=f"system assign_fisma_impact_level {fisma_impact_level}", + event=f"system assign_security_sensitivity_level {security_sensitivity_level}", object={"object": "system", "id": system.id, "name": system.root_element.name, "statementid": smt.id}, user={"id": request.user.id, "username": request.user.username} ) diff --git a/siteapp/tests.py b/siteapp/tests.py index c817c2a64..16bd6497b 100644 --- a/siteapp/tests.py +++ b/siteapp/tests.py @@ -1488,21 +1488,21 @@ def test_display_impact_level(self): # Display imact level testing # New project should not be categorized - self.assertInNodeText("Mission Impact: Not Categorized", "#systems-fisma-impact-level") + self.assertInNodeText("Mission Impact: Not Categorized", "#systems-security-sensitivity-level") # Update impact level # Get project.system.root_element to attach statement holding fisma impact level project = self.current_project fil = "Low" - # Test change and test system fisma_impact_level set/get methods - project.system.set_fisma_impact_level(fil) + # Test change and test system security_sensitivity_level set/get methods + project.system.set_security_sensitivity_level(fil) # Check value changed worked - self.assertEqual(project.system.get_fisma_impact_level, fil) + self.assertEqual(project.system.get_security_sensitivity_level, fil) # Refresh project page self.click_element('#btn-project-home') # See if project page has changed - wait_for_sleep_after( lambda: self.assertInNodeText("low", "#systems-fisma-impact-level") ) - impact_level_smts = project.system.root_element.statements_consumed.filter(statement_type=StatementTypeEnum.FISMA_IMPACT_LEVEL.value) + wait_for_sleep_after( lambda: self.assertInNodeText("low", "#systems-security-sensitivity-level") ) + impact_level_smts = project.system.root_element.statements_consumed.filter(statement_type=StatementTypeEnum.SECURITY_SENSITIVITY_LEVEL.value) self.assertEqual(impact_level_smts.count(), 1) diff --git a/siteapp/views.py b/siteapp/views.py index c5bc97118..10327d7e2 100644 --- a/siteapp/views.py +++ b/siteapp/views.py @@ -972,12 +972,12 @@ def project(request, project): if approx_compliance_degrees > 358: approx_compliance_degrees = 358 - # Fetch statement defining FISMA impact level if set - impact_level_smts = project.system.root_element.statements_consumed.filter(statement_type=StatementTypeEnum.FISMA_IMPACT_LEVEL.value) - if len(impact_level_smts) > 0: - impact_level = impact_level_smts.first().body + # Fetch statement defining Security Sensitivity level if set + security_sensitivity_smts = project.system.root_element.statements_consumed.filter(statement_type=StatementTypeEnum.SECURITY_SENSITIVITY_LEVEL.value) + if len(security_sensitivity_smts) > 0: + security_sensitivity = security_sensitivity_smts.first().body else: - impact_level = None + security_sensitivity = None security_objective_smt = project.system.root_element.statements_consumed.filter(statement_type=StatementTypeEnum.SECURITY_IMPACT_LEVEL.value) if security_objective_smt.exists(): @@ -992,7 +992,7 @@ def project(request, project): return render(request, "project.html", { "is_project_page": True, "project": project, - "impact_level": impact_level, + "security_sensitivity": security_sensitivity, "confidentiality": confidentiality, "integrity": integrity, "availability": availability, @@ -1059,7 +1059,6 @@ def project_security_objs_edit(request, project_id): # project to update project = Project.objects.get(id=project_id) - # TODO: Move security impact levels to an admin only form. adding validation. confidentiality = request.POST.get("confidentiality", "").strip() or None integrity = request.POST.get("integrity", "").strip() or None availability = request.POST.get("availability", "").strip() or None diff --git a/templates/project.html b/templates/project.html index e3d795980..669409b41 100644 --- a/templates/project.html +++ b/templates/project.html @@ -199,9 +199,9 @@

{{ poam_status_count.Open }}

#B2E0B2 0 235deg);"> {{ percent_compliant_100|floatformat:2 }}% compliance (unassessed)
- - {% if impact_level %} - Mission Impact: {{ impact_level|lower }} + + {% if security_sensitivity %} + Mission Impact: {{ security_sensitivity|lower }} {% else %} Mission Impact: Not Categorized {% endif %} From e7e8b9c557dfad6b9694497e41be4dc6be8563da Mon Sep 17 00:00:00 2001 From: davidpofo Date: Sat, 19 Jun 2021 11:35:25 -0400 Subject: [PATCH 03/28] td not th --- templates/components/compare_components.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/components/compare_components.html b/templates/components/compare_components.html index 80f958564..d022fea2a 100644 --- a/templates/components/compare_components.html +++ b/templates/components/compare_components.html @@ -49,7 +49,7 @@

{{ compare_prime.name }}

{% for prime_smt in prime_smts %} {{ compare_prime.name }} - {{ prime_smt.sid }}{% if prime_smt.pid %}.{{ prime_smt.pid }}{% endif %} + {{ prime_smt.sid }}{% if prime_smt.pid %}.{{ prime_smt.pid }}{% endif %} {{ prime_smt.sid_class }} {{ prime_smt.body|safe }} From 4fe11c01eb5e38404687738c423eceec784dab48 Mon Sep 17 00:00:00 2001 From: davidpofo Date: Sat, 19 Jun 2021 11:35:25 -0400 Subject: [PATCH 04/28] td not th --- templates/components/compare_components.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/components/compare_components.html b/templates/components/compare_components.html index 80f958564..d022fea2a 100644 --- a/templates/components/compare_components.html +++ b/templates/components/compare_components.html @@ -49,7 +49,7 @@

{{ compare_prime.name }}

{% for prime_smt in prime_smts %} {{ compare_prime.name }} - {{ prime_smt.sid }}{% if prime_smt.pid %}.{{ prime_smt.pid }}{% endif %} + {{ prime_smt.sid }}{% if prime_smt.pid %}.{{ prime_smt.pid }}{% endif %} {{ prime_smt.sid_class }} {{ prime_smt.body|safe }} From 5a9f3b98658dd64627483b1cd2348936de6567e9 Mon Sep 17 00:00:00 2001 From: davidpofo Date: Mon, 28 Jun 2021 08:45:18 -0400 Subject: [PATCH 05/28] Revert "td not th" This reverts commit e7e8b9c5 --- templates/components/compare_components.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/components/compare_components.html b/templates/components/compare_components.html index d022fea2a..80f958564 100644 --- a/templates/components/compare_components.html +++ b/templates/components/compare_components.html @@ -49,7 +49,7 @@

{{ compare_prime.name }}

{% for prime_smt in prime_smts %} {{ compare_prime.name }} - {{ prime_smt.sid }}{% if prime_smt.pid %}.{{ prime_smt.pid }}{% endif %} + {{ prime_smt.sid }}{% if prime_smt.pid %}.{{ prime_smt.pid }}{% endif %} {{ prime_smt.sid_class }} {{ prime_smt.body|safe }} From 360a035bab8af2c165ded282b5e20cdf8f2ef6b4 Mon Sep 17 00:00:00 2001 From: davidpofo Date: Mon, 28 Jun 2021 10:18:11 -0400 Subject: [PATCH 06/28] these values are safe --- templates/index.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/templates/index.html b/templates/index.html index 92d3a9823..416104511 100644 --- a/templates/index.html +++ b/templates/index.html @@ -17,7 +17,7 @@
From 892f18b4e9a9cc625b493e522a02f9e0bd1861f8 Mon Sep 17 00:00:00 2001 From: davidpofo Date: Mon, 28 Jun 2021 10:19:13 -0400 Subject: [PATCH 07/28] removing extra differences obj. --- controls/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controls/views.py b/controls/views.py index 341b4ed27..7765a0765 100644 --- a/controls/views.py +++ b/controls/views.py @@ -433,6 +433,7 @@ def compare_components(request): Compare submitted components """ # TODO: need to figure out how to accumulate all checked boxes not one in pageobj + # Might need to check for any previously entered checkbox ids compare_list = request.POST.getlist('componentcomparecheckbox') if compare_list: element_list = list(Element.objects.filter(pk__in=compare_list).exclude(element_type='system').distinct()) @@ -443,7 +444,6 @@ def compare_components(request): messages.add_message(request, messages.WARNING, f"Not enough components were selected to compare!") return HttpResponseRedirect("/controls/components") difference_tuples = [] - differences = [] for component in element_list: differences = [] # compare each component's statements to prime From e4e38a1dbfd323edcf70f0cfdc0898665a7b7c32 Mon Sep 17 00:00:00 2001 From: davidpofo Date: Mon, 28 Jun 2021 20:22:07 -0400 Subject: [PATCH 08/28] safe and efficiency --- controls/views.py | 14 +++++++------- siteapp/views.py | 6 +++++- templates/components/compare_components.html | 10 +++++----- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/controls/views.py b/controls/views.py index 7765a0765..a0b1e9e47 100644 --- a/controls/views.py +++ b/controls/views.py @@ -432,17 +432,17 @@ def compare_components(request): """ Compare submitted components """ - # TODO: need to figure out how to accumulate all checked boxes not one in pageobj - # Might need to check for any previously entered checkbox ids + # TODO: need to figure out how to accumulate all checked boxes not one in pageobj. Need to check for any previously entered checkbox ids compare_list = request.POST.getlist('componentcomparecheckbox') - if compare_list: - element_list = list(Element.objects.filter(pk__in=compare_list).exclude(element_type='system').distinct()) - compare_prime, element_list = element_list[0], element_list[1:]# The first component selected will be compared against the rest - compare_prime_smts = compare_prime.statements(StatementTypeEnum.CONTROL_IMPLEMENTATION_PROTOTYPE.value) - elif len(compare_list) <= 1: + if len(compare_list) <= 1: # add messages messages.add_message(request, messages.WARNING, f"Not enough components were selected to compare!") return HttpResponseRedirect("/controls/components") + else: + element_list = list(Element.objects.filter(pk__in=compare_list).exclude(element_type='system').distinct()) + compare_prime, element_list = element_list[0], element_list[ + 1:] # The first component selected will be compared against the rest + compare_prime_smts = compare_prime.statements(StatementTypeEnum.CONTROL_IMPLEMENTATION_PROTOTYPE.value) difference_tuples = [] for component in element_list: differences = [] diff --git a/siteapp/views.py b/siteapp/views.py index f47a364af..f1dbd4966 100644 --- a/siteapp/views.py +++ b/siteapp/views.py @@ -155,8 +155,12 @@ def subvars(s): return HttpResponseRedirect('/') # reload if settings.OKTA_CONFIG: return HttpResponseRedirect("/oidc/authenticate") + # Since there is only one hide registration entry in the SystemSettings table just retrieve the value directly to avoid + # 3 queries + hide_registration = SystemSettings.objects.filter(setting="hide_registration")[0].active + return render(request, "index.html", { - "hide_registration": SystemSettings.hide_registration, + "hide_registration": hide_registration, "sitename": Sitename.objects.last(), "signup_form": signup_form, "login_form": login_form, diff --git a/templates/components/compare_components.html b/templates/components/compare_components.html index d022fea2a..e94f9526f 100644 --- a/templates/components/compare_components.html +++ b/templates/components/compare_components.html @@ -29,12 +29,12 @@ {% block body %} -
+

Component comparison

-

{{ compare_prime.name }}

+

{{ compare_prime.name|safe }}

@@ -48,9 +48,9 @@

{{ compare_prime.name }}

{% for prime_smt in prime_smts %} - - - + + + {% endfor %} From f3849f3c0c30ad6bbc3f6b60d47b6d47a5d597d2 Mon Sep 17 00:00:00 2001 From: davidpofo Date: Tue, 29 Jun 2021 07:41:15 -0400 Subject: [PATCH 09/28] adding select/deselect all. checkbox container wrap. --- templates/components/component_library.html | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/templates/components/component_library.html b/templates/components/component_library.html index 668911032..c6b61bf8f 100644 --- a/templates/components/component_library.html +++ b/templates/components/component_library.html @@ -59,7 +59,9 @@

Component Library

{% csrf_token %}
- + + +
{% include 'components/paginate_comp.html' %}
@@ -69,6 +71,7 @@

Component Library

Select
Statements
+
{% for component in page_obj %}
@@ -79,7 +82,7 @@

Component Library

{% for tag in component.tags.all %}{{ tag.label }} {% endfor %}
- +
{% if component.get_control_impl_smts_prototype_count > 0 %}{{ component.get_control_impl_smts_prototype_count }} control{{ component.get_control_impl_smts_prototype_count|pluralize }}{% else %} @@ -87,6 +90,7 @@

Component Library

{% endfor %} +
{% include "components/import-component-modal.html" %} @@ -97,7 +101,7 @@

Component Library

{% block scripts %} {% endblock %} From 538a5de653ce27a322400b31eb85b8fdd04e031b Mon Sep 17 00:00:00 2001 From: davidpofo Date: Tue, 29 Jun 2021 08:52:27 -0400 Subject: [PATCH 10/28] control structure for compare button toggle --- templates/components/component_library.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/templates/components/component_library.html b/templates/components/component_library.html index c6b61bf8f..88f7bdafc 100644 --- a/templates/components/component_library.html +++ b/templates/components/component_library.html @@ -114,7 +114,9 @@

Component Library

var checkboxes = $('input[type=checkbox]') checkboxes.prop('checked', val); var compare_btn = $("#compare-components") - compare_btn.toggle() + if (checkboxes.prop('checked') && compare_btn.is(":hidden") ) { + compare_btn.toggle() + } } {% endblock %} From c8390770e9d786036fd00a103f4026e1cbcb567b Mon Sep 17 00:00:00 2001 From: davidpofo Date: Tue, 29 Jun 2021 09:51:43 -0400 Subject: [PATCH 11/28] Maintain sort order of compare_list otherwise Django will order ascending --- controls/views.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/controls/views.py b/controls/views.py index a0b1e9e47..5e806c230 100644 --- a/controls/views.py +++ b/controls/views.py @@ -439,7 +439,9 @@ def compare_components(request): messages.add_message(request, messages.WARNING, f"Not enough components were selected to compare!") return HttpResponseRedirect("/controls/components") else: - element_list = list(Element.objects.filter(pk__in=compare_list).exclude(element_type='system').distinct()) + ele_q = Element.objects.filter(pk__in=compare_list).exclude(element_type='system').distinct() + # Maintain sort order of compare_list otherwise Django will order ascending + element_list = sorted(ele_q, key=lambda x: compare_list.index(str(x.id))) compare_prime, element_list = element_list[0], element_list[ 1:] # The first component selected will be compared against the rest compare_prime_smts = compare_prime.statements(StatementTypeEnum.CONTROL_IMPLEMENTATION_PROTOTYPE.value) From 043823974fa43fa25f2a0cbb3a56d2e69604d6f9 Mon Sep 17 00:00:00 2001 From: davidpofo Date: Tue, 29 Jun 2021 10:46:55 -0400 Subject: [PATCH 12/28] adding change component button to change what the prime component of comparison is. Still has work todo --- controls/views.py | 6 +- templates/components/compare_block.html | 22 ++++-- templates/components/compare_components.html | 83 +++++++++++--------- templates/components/component_library.html | 2 +- 4 files changed, 64 insertions(+), 49 deletions(-) diff --git a/controls/views.py b/controls/views.py index 5e806c230..4ab8c7959 100644 --- a/controls/views.py +++ b/controls/views.py @@ -460,14 +460,16 @@ def compare_components(request): else: diff = f"{smt.body}" differences.append(diff) - difference_tuples.extend(zip([component.name] * len(cmt_smts), cmt_smts, differences)) + difference_tuples.extend(zip([component.id] * len(cmt_smts),[component.name] * len(cmt_smts), cmt_smts, differences)) + # TODO: still need to figure out how to only have only checkboxes for each component and send that to the view if request.method == 'POST': context = { "element_list": element_list, "compare_prime": compare_prime, "prime_smts": compare_prime_smts, "secondary_smts": cmt_smts, - "differences": difference_tuples + "differences": difference_tuples, + "compare_list": compare_list } return render(request, "components/compare_components.html", context) diff --git a/templates/components/compare_block.html b/templates/components/compare_block.html index de7bb4fd7..d43b42a3c 100644 --- a/templates/components/compare_block.html +++ b/templates/components/compare_block.html @@ -8,17 +8,23 @@

Compared components

+ -{% for cmp_name, smt, diff in comp_differences %} - - - - - - -{% endfor %} + + {% for comp_id, cmp_name, smt, diff in comp_differences %} + + + + + + + + + + {% endfor %} +
{{ compare_prime.name }}{{ prime_smt.sid }}{% if prime_smt.pid %}.{{ prime_smt.pid }}{% endif %}{{ prime_smt.sid_class }}{{ compare_prime.name|safe }}{{ prime_smt.sid|safe }}{% if prime_smt.pid %}.{{ prime_smt.pid|safe }}{% endif %}{{ prime_smt.sid_class|safe }} {{ prime_smt.body|safe }}
Control Catalog StatementSelect
{{ cmp_name }}{{ smt.sid }}{% if smt.pid %}.{{ smt.pid }}{% endif %}{{ smt.sid_class }}{{ diff|safe }}
{{ cmp_name }}{{ smt.sid }}{% if smt.pid %}.{{ smt.pid }}{% endif %}{{ smt.sid_class }}{{ diff|safe }}{{ id_list }}
diff --git a/templates/components/compare_components.html b/templates/components/compare_components.html index e94f9526f..3db704ac2 100644 --- a/templates/components/compare_components.html +++ b/templates/components/compare_components.html @@ -9,17 +9,17 @@ {% block head %} {% include "controls/_style-controls.html" %} @@ -28,45 +28,52 @@ {% block contextbar %}{% endblock %} {% block body %} - +
+ {% csrf_token %}
-
-

Component comparison

-
-

{{ compare_prime.name|safe }}

-
- - - - - - - - - - - {% for prime_smt in prime_smts %} +
+
+ +
+

Component comparison

+ +
+

{{ compare_prime.name|safe }}

+
+
ComponentControlCatalogStatement
+ + + + + + + + + + + {% for prime_smt in prime_smts %} + - {% endfor %} - -
ComponentControlCatalogStatementSelect
{{ compare_prime.name|safe }} {{ prime_smt.sid|safe }}{% if prime_smt.pid %}.{{ prime_smt.pid|safe }}{% endif %} {{ prime_smt.sid_class|safe }} {{ prime_smt.body|safe }}
+ {% endfor %} + + +
-
-
- {% with comp_differences=differences %} - {% include "components/compare_block.html" %} - {% endwith %} -
- -
+
+ {% with comp_differences=differences %} + {% include "components/compare_block.html" %} + {% endwith %} +
+
+ {% endblock %} diff --git a/templates/components/component_library.html b/templates/components/component_library.html index 88f7bdafc..97a9898df 100644 --- a/templates/components/component_library.html +++ b/templates/components/component_library.html @@ -82,7 +82,7 @@

Component Library

{% for tag in component.tags.all %}{{ tag.label }} {% endfor %}
- +
{% if component.get_control_impl_smts_prototype_count > 0 %}{{ component.get_control_impl_smts_prototype_count }} control{{ component.get_control_impl_smts_prototype_count|pluralize }}{% else %} From cbfbac854a63fb358a2914a7b63d0ed5169c8a40 Mon Sep 17 00:00:00 2001 From: davidpofo Date: Wed, 30 Jun 2021 07:35:36 -0400 Subject: [PATCH 13/28] remove commented out code from template --- templates/portfolios/detail.html | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/templates/portfolios/detail.html b/templates/portfolios/detail.html index 258d6efec..765d8433d 100644 --- a/templates/portfolios/detail.html +++ b/templates/portfolios/detail.html @@ -78,18 +78,7 @@

{{ portfolio.title }}

{{project.id}}
{{project.portfolio.title}}
- + {% get_obj_perms request.user for project as "perms" %} {% get_obj_perms request.user for project.portfolio as "portfolio_perms" %} {% if "delete_project" in perms %} From db86b7c648a696d02a166ffc316130f578c8b6f8 Mon Sep 17 00:00:00 2001 From: davidpofo Date: Wed, 30 Jun 2021 09:02:32 -0400 Subject: [PATCH 14/28] fixing styling of portfolio table --- templates/portfolios/portfolios_table.html | 27 +++++++++++----------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/templates/portfolios/portfolios_table.html b/templates/portfolios/portfolios_table.html index d619172b9..6b5394ddd 100644 --- a/templates/portfolios/portfolios_table.html +++ b/templates/portfolios/portfolios_table.html @@ -5,18 +5,18 @@ {% if portfolios %}
-
Portfolio
-
ID
-
Role
-
Manage
-
Created
+
Portfolio
+
ID
+
Role
+
Manage
+
Created
{% endif %} {% for portfolio in portfolios %}
-
+
{{portfolio.title}} @@ -27,25 +27,24 @@ {% get_obj_perms request.user for portfolio as "perms" %} {% if "can_grant_portfolio_owner_permission" in perms %} -
Owner
-
+
Owner
+
{% elif "change_portfolio" in perms%} -
Portfolio Member
-
-
+
Portfolio Member
+
{% else %} -
Project Member
-
+
Project Member
+
No Permission
{% endif %} -
+
{{portfolio.created|naturaltime}}
{% endfor %} From ab6bb5e22dedfa385f88b9d896061c43109aa03b Mon Sep 17 00:00:00 2001 From: davidpofo Date: Wed, 30 Jun 2021 09:05:00 -0400 Subject: [PATCH 15/28] using django guardian ObjectPermissionChecker to prefetch permissions. Directly check permissions, to avoid N+1 query of perms with get_obj_perms --- siteapp/views.py | 18 +++++++++++------- templates/portfolios/detail.html | 14 ++++++-------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/siteapp/views.py b/siteapp/views.py index f1dbd4966..615894ba8 100644 --- a/siteapp/views.py +++ b/siteapp/views.py @@ -19,6 +19,7 @@ from django.utils import timezone from django.views.decorators.http import require_http_methods from django.views.generic import ListView +from guardian.core import ObjectPermissionChecker from guardian.decorators import permission_required_or_403 from guardian.shortcuts import get_perms_for_model, get_perms, assign_perm @@ -1886,20 +1887,23 @@ def g(request, pk): def portfolio_projects(request, pk): """List of projects within a portfolio""" portfolio = Portfolio.objects.get(pk=pk) - projects = Project.objects.filter(portfolio=portfolio).select_related('root_task') \ + projects = Project.objects.filter(portfolio=portfolio).select_related('root_task').prefetch_related('portfolio') \ .exclude(is_organization_project=True).order_by('-created') - user_projects = [project for project in projects if request.user.has_perm('view_project', project)] + # # Prefetch the permissions + perm_checker = ObjectPermissionChecker(request.user) + perm_checker.prefetch_perms(projects) + + user_projects = [project for project in projects if perm_checker.has_perm('view_project', project)] anonymous_user = User.objects.get(username='AnonymousUser') users_with_perms = portfolio.users_with_perms() return render(request, "portfolios/detail.html", { "portfolio": portfolio, - "projects": projects if request.user.has_perm('view_portfolio', portfolio) else user_projects, - "can_invite_to_portfolio": request.user.has_perm('can_grant_portfolio_owner_permission', portfolio), - "can_edit_portfolio": request.user.has_perm('change_portfolio', portfolio), - "send_invitation": Invitation.form_context_dict(request.user, portfolio, [request.user, anonymous_user]), + "projects": projects if perm_checker.has_perm('view_portfolio', portfolio) else user_projects, + "can_invite_to_portfolio": perm_checker.has_perm('can_grant_portfolio_owner_permission', portfolio), + "can_edit_portfolio": perm_checker.has_perm('change_portfolio', portfolio), + "send_invitation": Invitation.form_context_dict(perm_checker, portfolio, [request.user, anonymous_user]), "users_with_perms": users_with_perms, - "display_users_with_perms": len(users_with_perms), }) diff --git a/templates/portfolios/detail.html b/templates/portfolios/detail.html index 765d8433d..4eea9f572 100644 --- a/templates/portfolios/detail.html +++ b/templates/portfolios/detail.html @@ -1,6 +1,5 @@ {% extends "base.html" %} {% load humanize %} -{% load guardian_tags %} {% load static %} {% load q %} @@ -79,13 +78,12 @@

{{ portfolio.title }}

{{project.portfolio.title}}
- {% get_obj_perms request.user for project as "perms" %} - {% get_obj_perms request.user for project.portfolio as "portfolio_perms" %} - {% if "delete_project" in perms %} + + {% if perms.siteapp.delete_project %}
Owner
- {% elif "change_project" in perms%} + {% elif perms.siteapp.change_project %}
Project Member
- {% elif "portfolio_perms"%} + {% else %}
Portfolio Member
{% endif %} @@ -117,7 +115,7 @@

A portfolio is a collection of projects.

{% endif %} - {% if display_users_with_perms %} + {% if users_with_perms is not None %}

@@ -165,7 +163,7 @@

A portfolio is a collection of projects.

{{ block.super }} {% endblock %} - {% block scripts %} +{% block scripts %} {% endblock %} From e1091fa9e248b75b28c3b8ac4fee8984805b2504 Mon Sep 17 00:00:00 2001 From: davidpofo Date: Thu, 1 Jul 2021 01:50:05 -0400 Subject: [PATCH 19/28] test test_portfolio_projects --- siteapp/tests.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/siteapp/tests.py b/siteapp/tests.py index aa24e57c8..d447680b0 100644 --- a/siteapp/tests.py +++ b/siteapp/tests.py @@ -801,6 +801,29 @@ def test_create_portfolio_project(self): self.click_element("#create-portfolio-button") wait_for_sleep_after(lambda: self.assertRegex(self.browser.title, "Security Projects")) + def test_portfolio_projects(self): + """ + Ensure key parts of the portfolio page + """ + # Create new project within portfolio + self._login() + self._new_project() + + portfolio_id = Project.objects.last().id + url = reverse('portfolio_projects', args=[portfolio_id]) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'portfolios/detail.html') + self.assertContains(response, 'Owner', 1) + # Context + bool_context_objects = ["can_invite_to_portfolio", "can_edit_portfolio"] + for context in bool_context_objects: + self.assertEqual(response.context[context], True) + + self.assertEqual(response.context["portfolio"].id, portfolio_id) + self.assertEqual(response.context["send_invitation"]['users'], [{'id': 2, 'name': 'me'}, {'id': 3, 'name': 'me2'}]) + + def test_grant_portfolio_access(self): # Grant another member access to portfolio self._login() From 781bf7029191a978195c68c9b3ac8314fb725f14 Mon Sep 17 00:00:00 2001 From: davidpofo Date: Thu, 1 Jul 2021 10:22:12 -0400 Subject: [PATCH 20/28] name not value for statement enums --- controls/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/controls/views.py b/controls/views.py index eb2195bf5..c53595661 100644 --- a/controls/views.py +++ b/controls/views.py @@ -444,12 +444,12 @@ def compare_components(request): element_list = sorted(ele_q, key=lambda x: compare_list.index(str(x.id))) compare_prime, element_list = element_list[0], element_list[ 1:] # The first component selected will be compared against the rest - compare_prime_smts = compare_prime.statements(StatementTypeEnum.CONTROL_IMPLEMENTATION_PROTOTYPE.value) + compare_prime_smts = compare_prime.statements(StatementTypeEnum.CONTROL_IMPLEMENTATION_PROTOTYPE.name) difference_tuples = [] for component in element_list: differences = [] # compare each component's statements to prime - cmt_smts = component.statements(StatementTypeEnum.CONTROL_IMPLEMENTATION_PROTOTYPE.value) + cmt_smts = component.statements(StatementTypeEnum.CONTROL_IMPLEMENTATION_PROTOTYPE.name) if cmt_smts.exists(): # TODO: Need to create a tuple with smt id to return appropriate for smt in cmt_smts: From a05e13b4a8151f050307eec0f1c64738e881faca Mon Sep 17 00:00:00 2001 From: davidpofo Date: Fri, 2 Jul 2021 09:23:47 -0400 Subject: [PATCH 21/28] name not value for enums --- controls/models.py | 32 ++++++++++++++++---------------- siteapp/tests.py | 2 +- siteapp/views.py | 4 ++-- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/controls/models.py b/controls/models.py index 1b08b6b6e..1dd82f3bd 100644 --- a/controls/models.py +++ b/controls/models.py @@ -118,7 +118,7 @@ def create_prototype(self): return self.prototype # check if prototype content is the same, report error if not, or overwrite if permission approved prototype = deepcopy(self) - prototype.statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION_PROTOTYPE.value + prototype.statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION_PROTOTYPE.name prototype.consumer_element_id = None prototype.id = None prototype.save() @@ -135,7 +135,7 @@ def create_instance_from_prototype(self, consumer_element_id): # System already has instance of the control_implementation statement # TODO: write check for this logic # Get all statements for consumer element so we can identify - smts_existing = Statement.objects.filter(consumer_element__id = consumer_element_id, statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION.value).select_related('prototype') + smts_existing = Statement.objects.filter(consumer_element__id = consumer_element_id, statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION.name).select_related('prototype') # Get prototype ids for all consumer element statements smts_existing_prototype_ids = [smt.prototype.id for smt in smts_existing] @@ -146,7 +146,7 @@ def create_instance_from_prototype(self, consumer_element_id): # # check if prototype content is the same, report error if not, or overwrite if permission approved instance = deepcopy(self) - instance.statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION.value + instance.statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION.name instance.consumer_element_id = consumer_element_id instance.id = None # Set prototype attribute to newly created instance @@ -247,7 +247,7 @@ class Element(auto_prefetch.Model, TagModelMixin): # e.statements_consumed.all() # # Retrieve statements that are control implementations - # e.statements_consumed.filter(statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION.value) + # e.statements_consumed.filter(statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION.name) def __str__(self): return "'%s id=%d'" % (self.name, self.id) @@ -340,7 +340,7 @@ def statements(self, statement_type): def get_control_impl_smts_prototype_count(self): """Return count of statements with this element as producer_element""" - smt_count = Statement.objects.filter(producer_element=self, statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION_PROTOTYPE.value).count() + smt_count = Statement.objects.filter(producer_element=self, statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION_PROTOTYPE.name).count() return smt_count @@ -473,7 +473,7 @@ class System(auto_prefetch.Model): # Notes # Retrieve system implementation statements # system = System.objects.get(pk=2) - # system.root_element.statements_consumed.filter(statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION.value) + # system.root_element.statements_consumed.filter(statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION.name) # # Retrieve system common controls statements # system = System.objects.get(pk=2) @@ -526,7 +526,7 @@ def remove_control(self, control_id): control = ElementControl.objects.get(pk=control_id) # Delete Control Statements - self.root_element.statements_consumed.filter(statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION.value, + self.root_element.statements_consumed.filter(statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION.name, sid_class=control.oscal_catalog_key, sid=control.oscal_ctl_id ).delete() @@ -538,7 +538,7 @@ def remove_control(self, control_id): def set_security_sensitivity_level(self, security_sensitivity_level): """Assign Security Sensitivty level to system""" # Get or create the security_sensitivity_level smt for system's root_element; should only have 1 statement - smt = Statement.objects.create(statement_type=StatementTypeEnum.SECURITY_SENSITIVITY_LEVEL.value, producer_element=self.root_element, consumer_element=self.root_element, body=security_sensitivity_level) + smt = Statement.objects.create(statement_type=StatementTypeEnum.SECURITY_SENSITIVITY_LEVEL.name, producer_element=self.root_element, consumer_element=self.root_element, body=security_sensitivity_level) return security_sensitivity_level, smt @property @@ -546,7 +546,7 @@ def get_security_sensitivity_level(self): """Assign Security Sensitivty level to system""" # Get or create the security_sensitivity_level smt for system's root_element; should only have 1 statement - smt, created = Statement.objects.get_or_create(statement_type=StatementTypeEnum.SECURITY_SENSITIVITY_LEVEL.value, producer_element=self.root_element, consumer_element=self.root_element) + smt, created = Statement.objects.get_or_create(statement_type=StatementTypeEnum.SECURITY_SENSITIVITY_LEVEL.name, producer_element=self.root_element, consumer_element=self.root_element) security_sensitivity_level = smt.body return security_sensitivity_level @@ -554,12 +554,12 @@ def get_security_sensitivity_level(self): def set_security_impact_level(self, security_impact_level): """Assign one or more of the System Security impact levels (e.g. confidentiality, integrity, availability)""" - security_objective_smt = self.root_element.statements_consumed.filter(statement_type=StatementTypeEnum.SECURITY_IMPACT_LEVEL.value) + security_objective_smt = self.root_element.statements_consumed.filter(statement_type=StatementTypeEnum.SECURITY_IMPACT_LEVEL.name) if security_objective_smt.exists(): security_objective_smt.update(body=security_impact_level) else: # Set the security_impact_level smt for element; should only have 1 statement - security_objective_smt, created = Statement.objects.get_or_create(statement_type=StatementTypeEnum.SECURITY_IMPACT_LEVEL.value, producer_element=self.root_element,consumer_element=self.root_element, body=security_impact_level) + security_objective_smt, created = Statement.objects.get_or_create(statement_type=StatementTypeEnum.SECURITY_IMPACT_LEVEL.name, producer_element=self.root_element,consumer_element=self.root_element, body=security_impact_level) return security_impact_level, security_objective_smt @property @@ -567,7 +567,7 @@ def get_security_impact_level(self): """Get one or more of the System Security impact levels (e.g. confidentiality, integrity, availability)""" # Get the security_impact_level smt for element; should only have 1 statement - smt = Statement.objects.get(statement_type=StatementTypeEnum.SECURITY_IMPACT_LEVEL.value, producer_element=self.root_element, consumer_element=self.root_element) + smt = Statement.objects.get(statement_type=StatementTypeEnum.SECURITY_IMPACT_LEVEL.name, producer_element=self.root_element, consumer_element=self.root_element) security_impact_level = eval(smt.body)# Evaluate string of dictionary return security_impact_level @@ -584,7 +584,7 @@ def smts_common_controls_as_dict(self): @property def smts_control_implementation_as_dict(self): - smts = self.root_element.statements_consumed.filter(statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION.value).order_by('pid') + smts = self.root_element.statements_consumed.filter(statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION.name).order_by('pid') smts_as_dict = {} for smt in smts: if smt.sid in smts_as_dict: @@ -601,7 +601,7 @@ def control_implementation_as_dict(self): elm = self.root_element selected_controls = elm.controls.all().values("oscal_ctl_id", "uuid") # Get the smts_control_implementations ordered by part, e.g. pid - smts = elm.statements_consumed.filter(statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION.value).order_by('pid') + smts = elm.statements_consumed.filter(statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION.name).order_by('pid') smts_as_dict = {} @@ -688,12 +688,12 @@ def controls_status_count(self): # Fetch all selected controls elm = self.root_element - counts = Statement.objects.filter(statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION.value, status__in=status_list).values('status').order_by('status').annotate(count=Count('status')) + counts = Statement.objects.filter(statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION.name, status__in=status_list).values('status').order_by('status').annotate(count=Count('status')) status_stats.update({r['status']: r['count'] for r in counts}) # TODO add index on statement status # Get overall controls addressed (e.g., covered) - status_stats['Addressed'] = elm.statements_consumed.filter(statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION.value).values('sid').distinct().count() + status_stats['Addressed'] = elm.statements_consumed.filter(statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION.name).values('sid').distinct().count() return status_stats @cached_property diff --git a/siteapp/tests.py b/siteapp/tests.py index 16bd6497b..30d5f09c0 100644 --- a/siteapp/tests.py +++ b/siteapp/tests.py @@ -1502,7 +1502,7 @@ def test_display_impact_level(self): self.click_element('#btn-project-home') # See if project page has changed wait_for_sleep_after( lambda: self.assertInNodeText("low", "#systems-security-sensitivity-level") ) - impact_level_smts = project.system.root_element.statements_consumed.filter(statement_type=StatementTypeEnum.SECURITY_SENSITIVITY_LEVEL.value) + impact_level_smts = project.system.root_element.statements_consumed.filter(statement_type=StatementTypeEnum.SECURITY_SENSITIVITY_LEVEL.name) self.assertEqual(impact_level_smts.count(), 1) diff --git a/siteapp/views.py b/siteapp/views.py index 10327d7e2..56f18f683 100644 --- a/siteapp/views.py +++ b/siteapp/views.py @@ -973,13 +973,13 @@ def project(request, project): approx_compliance_degrees = 358 # Fetch statement defining Security Sensitivity level if set - security_sensitivity_smts = project.system.root_element.statements_consumed.filter(statement_type=StatementTypeEnum.SECURITY_SENSITIVITY_LEVEL.value) + security_sensitivity_smts = project.system.root_element.statements_consumed.filter(statement_type=StatementTypeEnum.SECURITY_SENSITIVITY_LEVEL.name) if len(security_sensitivity_smts) > 0: security_sensitivity = security_sensitivity_smts.first().body else: security_sensitivity = None - security_objective_smt = project.system.root_element.statements_consumed.filter(statement_type=StatementTypeEnum.SECURITY_IMPACT_LEVEL.value) + security_objective_smt = project.system.root_element.statements_consumed.filter(statement_type=StatementTypeEnum.SECURITY_IMPACT_LEVEL.name) if security_objective_smt.exists(): security_body = project.system.get_security_impact_level confidentiality, integrity, availability = security_body.get('security_objective_confidentiality', From b7ae55f6522a2f67312b5ae87a68ae5b62452c4d Mon Sep 17 00:00:00 2001 From: davidpofo Date: Fri, 2 Jul 2021 10:16:17 -0400 Subject: [PATCH 22/28] missed one get --- controls/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controls/models.py b/controls/models.py index 1ade37354..acf3fc1a4 100644 --- a/controls/models.py +++ b/controls/models.py @@ -575,7 +575,7 @@ def get_security_impact_level(self): # Get the security_impact_level smt for element; should only have 1 statement try: - smt = Statement.objects.get(statement_type=StatementTypeEnum.SECURITY_IMPACT_LEVEL.value, producer_element=self.root_element, consumer_element=self.root_element) + smt = Statement.objects.get(statement_type=StatementTypeEnum.SECURITY_IMPACT_LEVEL.name, producer_element=self.root_element, consumer_element=self.root_element) security_impact_level = eval(smt.body)# Evaluate string of dictionary return security_impact_level except Statement.DoesNotExist: From 68a48855e526e5a942502b989be92791631e3d77 Mon Sep 17 00:00:00 2001 From: davidpofo Date: Fri, 2 Jul 2021 11:37:15 -0400 Subject: [PATCH 23/28] need to force login as authenticated user and then reset login --- siteapp/tests.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/siteapp/tests.py b/siteapp/tests.py index 9cf18d9c2..bd5479e9e 100644 --- a/siteapp/tests.py +++ b/siteapp/tests.py @@ -396,7 +396,7 @@ def _login(self, username=None, password=None): self.click_element("form#login_form button[type=submit]") def _new_project(self): - self.browser.get(self.url("/projects")) + self.browser.get(self.browser.current_url) wait_for_sleep_after(lambda: self.click_element("#new-project")) @@ -805,11 +805,15 @@ def test_portfolio_projects(self): """ Ensure key parts of the portfolio page """ - # Create new project within portfolio + # Login as authenticated user + self.client.force_login(user=self.user) + # Reset login + self.browser.get(self.url("/accounts/logout/")) self._login() + # If the above is not done a new project cannot be created self._new_project() - portfolio_id = Project.objects.last().id + portfolio_id = Project.objects.last().portfolio.id url = reverse('portfolio_projects', args=[portfolio_id]) response = self.client.get(url) self.assertEqual(response.status_code, 200) @@ -821,7 +825,6 @@ def test_portfolio_projects(self): self.assertEqual(response.context[context], True) self.assertEqual(response.context["portfolio"].id, portfolio_id) - self.assertEqual(response.context["send_invitation"]['users'], [{'id': 2, 'name': 'me'}, {'id': 3, 'name': 'me2'}]) def test_grant_portfolio_access(self): From 0783a297efeeedd85c5bfc998c066b91df0c2bda Mon Sep 17 00:00:00 2001 From: davidpofo Date: Fri, 2 Jul 2021 11:50:24 -0400 Subject: [PATCH 24/28] url --- siteapp/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/siteapp/tests.py b/siteapp/tests.py index bd5479e9e..0c95a5051 100644 --- a/siteapp/tests.py +++ b/siteapp/tests.py @@ -396,7 +396,7 @@ def _login(self, username=None, password=None): self.click_element("form#login_form button[type=submit]") def _new_project(self): - self.browser.get(self.browser.current_url) + self.browser.get(self.url("/projects")) wait_for_sleep_after(lambda: self.click_element("#new-project")) From bfc196b55bf268d1ffef7fe6cf5b6fdf3d752475 Mon Sep 17 00:00:00 2001 From: davidpofo Date: Fri, 2 Jul 2021 12:01:36 -0400 Subject: [PATCH 25/28] snyk update to avoid SQL injection vuln found in Django 3.2.4 --- requirements.in | 2 +- requirements.txt | 112 ++++++++++++++++++++++++++++------------------- 2 files changed, 67 insertions(+), 47 deletions(-) diff --git a/requirements.in b/requirements.in index 2d427499c..2d4452b2f 100644 --- a/requirements.in +++ b/requirements.in @@ -28,7 +28,7 @@ jsonschema # MIT License filetype # MIT License # Common Django Packages # Django==2.2.12 # BSD License -Django==3.2.4 # BSD License +Django==3.2.5 # BSD License django-debug-toolbar # BSD License django-allauth # MIT License django-bootstrap3 # BSD 3-Clause License diff --git a/requirements.txt b/requirements.txt index d716d720b..fcf3825b3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,9 +2,9 @@ appdirs==1.4.4 \ --hash=sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41 \ --hash=sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128 # via fs -asgiref==3.3.4 \ - --hash=sha256:92906c611ce6c967347bbfea733f13d6313901d54dcca88195eaeb52b2a8e8ee \ - --hash=sha256:d1216dfbdfb63826470995d31caed36225dcaf34f182e0fa257a4dd9e86f1b78 +asgiref==3.4.1 \ + --hash=sha256:4ef1ab46b484e3c706329cedeff284a5d40824200638503f5768edb6de7d58e9 \ + --hash=sha256:ffc141aa908e6f175673e7b1b3b7af4fdb0ecb738fc5c8b88f69f055c2415214 # via django attrs==21.2.0 \ --hash=sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1 \ @@ -187,9 +187,9 @@ dj-database-url==0.5.0 \ --hash=sha256:4aeaeb1f573c74835b0686a2b46b85990571159ffc21aa57ecd4d1e1cb334163 \ --hash=sha256:851785365761ebe4994a921b433062309eb882fedd318e1b0fcecc607ed02da9 # via -r requirements.in -django==3.2.4 \ - --hash=sha256:66c9d8db8cc6fe938a28b7887c1596e42d522e27618562517cc8929eb7e7f296 \ - --hash=sha256:ea735cbbbb3b2fba6d4da4784a0043d84c67c92f1fdf15ad6db69900e792c10f +django==3.2.5 \ + --hash=sha256:3da05fea54fdec2315b54a563d5b59f3b4e2b1e69c3a5841dda35019c01855cd \ + --hash=sha256:c58b5f19c5ae0afe6d75cbdd7df561e6eb929339985dbbda2565e1cabb19a62e # via # -r requirements.in # django-allauth @@ -545,41 +545,41 @@ pbr==5.6.0 \ --hash=sha256:42df03e7797b796625b1029c0400279c7c34fd7df24a7d7818a1abb5b38710dd \ --hash=sha256:c68c661ac5cc81058ac94247278eeda6d2e6aecb3e227b0387c30d277e7ef8d4 # via stevedore -pillow==8.2.0 \ - --hash=sha256:01425106e4e8cee195a411f729cff2a7d61813b0b11737c12bd5991f5f14bcd5 \ - --hash=sha256:031a6c88c77d08aab84fecc05c3cde8414cd6f8406f4d2b16fed1e97634cc8a4 \ - --hash=sha256:083781abd261bdabf090ad07bb69f8f5599943ddb539d64497ed021b2a67e5a9 \ - --hash=sha256:0d19d70ee7c2ba97631bae1e7d4725cdb2ecf238178096e8c82ee481e189168a \ - --hash=sha256:0e04d61f0064b545b989126197930807c86bcbd4534d39168f4aa5fda39bb8f9 \ - --hash=sha256:12e5e7471f9b637762453da74e390e56cc43e486a88289995c1f4c1dc0bfe727 \ - --hash=sha256:22fd0f42ad15dfdde6c581347eaa4adb9a6fc4b865f90b23378aa7914895e120 \ - --hash=sha256:238c197fc275b475e87c1453b05b467d2d02c2915fdfdd4af126145ff2e4610c \ - --hash=sha256:3b570f84a6161cf8865c4e08adf629441f56e32f180f7aa4ccbd2e0a5a02cba2 \ - --hash=sha256:463822e2f0d81459e113372a168f2ff59723e78528f91f0bd25680ac185cf797 \ - --hash=sha256:4d98abdd6b1e3bf1a1cbb14c3895226816e666749ac040c4e2554231068c639b \ - --hash=sha256:5afe6b237a0b81bd54b53f835a153770802f164c5570bab5e005aad693dab87f \ - --hash=sha256:5b70110acb39f3aff6b74cf09bb4169b167e2660dabc304c1e25b6555fa781ef \ - --hash=sha256:5cbf3e3b1014dddc45496e8cf38b9f099c95a326275885199f427825c6522232 \ - --hash=sha256:624b977355cde8b065f6d51b98497d6cd5fbdd4f36405f7a8790e3376125e2bb \ - --hash=sha256:63728564c1410d99e6d1ae8e3b810fe012bc440952168af0a2877e8ff5ab96b9 \ - --hash=sha256:66cc56579fd91f517290ab02c51e3a80f581aba45fd924fcdee01fa06e635812 \ - --hash=sha256:6c32cc3145928c4305d142ebec682419a6c0a8ce9e33db900027ddca1ec39178 \ - --hash=sha256:8b56553c0345ad6dcb2e9b433ae47d67f95fc23fe28a0bde15a120f25257e291 \ - --hash=sha256:8bb1e155a74e1bfbacd84555ea62fa21c58e0b4e7e6b20e4447b8d07990ac78b \ - --hash=sha256:95d5ef984eff897850f3a83883363da64aae1000e79cb3c321915468e8c6add5 \ - --hash=sha256:a013cbe25d20c2e0c4e85a9daf438f85121a4d0344ddc76e33fd7e3965d9af4b \ - --hash=sha256:a787ab10d7bb5494e5f76536ac460741788f1fbce851068d73a87ca7c35fc3e1 \ - --hash=sha256:a7d5e9fad90eff8f6f6106d3b98b553a88b6f976e51fce287192a5d2d5363713 \ - --hash=sha256:aac00e4bc94d1b7813fe882c28990c1bc2f9d0e1aa765a5f2b516e8a6a16a9e4 \ - --hash=sha256:b91c36492a4bbb1ee855b7d16fe51379e5f96b85692dc8210831fbb24c43e484 \ - --hash=sha256:c03c07ed32c5324939b19e36ae5f75c660c81461e312a41aea30acdd46f93a7c \ - --hash=sha256:c5236606e8570542ed424849f7852a0ff0bce2c4c8d0ba05cc202a5a9c97dee9 \ - --hash=sha256:c6b39294464b03457f9064e98c124e09008b35a62e3189d3513e5148611c9388 \ - --hash=sha256:cb7a09e173903541fa888ba010c345893cd9fc1b5891aaf060f6ca77b6a3722d \ - --hash=sha256:d68cb92c408261f806b15923834203f024110a2e2872ecb0bd2a110f89d3c602 \ - --hash=sha256:dc38f57d8f20f06dd7c3161c59ca2c86893632623f33a42d592f097b00f720a9 \ - --hash=sha256:e98eca29a05913e82177b3ba3d198b1728e164869c613d76d0de4bde6768a50e \ - --hash=sha256:f217c3954ce5fd88303fc0c317af55d5e0204106d86dea17eb8205700d47dec2 +pillow==8.3.0 \ + --hash=sha256:063d17a02a0170c2f880fbd373b2738b089c6adcbd1f7418667bc9e97524c11b \ + --hash=sha256:1037288a22cc8ec9d2918a24ded733a1cc4342fd7f21d15d37e6bbe5fb4a7306 \ + --hash=sha256:25f6564df21d15bcac142b4ed92b6c02e53557539f535f31c1f3bcc985484753 \ + --hash=sha256:28f184c0a65be098af412f78b0b6f3bbafd1614e1dc896e810d8357342a794b7 \ + --hash=sha256:3251557c53c1ed0c345559afc02d2b0a0aa5788042e161366ed90b27bc322d3d \ + --hash=sha256:331f8321418682386e4f0d0e6369f732053f95abddd2af4e1b1ef74a9537ef37 \ + --hash=sha256:333313bcc53a8a7359e98d5458dfe37bfa301da2fd0e0dc41f585ae0cede9181 \ + --hash=sha256:34ce3d993cb4ca840b1e31165b38cb19c64f64f822a8bc5565bde084baff3bdb \ + --hash=sha256:490c9236ef4762733b6c2e1f1fcb37793cb9c57d860aa84d6994c990461882e5 \ + --hash=sha256:519b3b24dedc81876d893475bade1b92c4ce7c24b9b82224f0bd8daae682e039 \ + --hash=sha256:53f6e4b73b3899015ac4aa95d99da0f48ea18a6d7c8db672e8bead3fb9570ef5 \ + --hash=sha256:561339ed7c324bbcb29b5e4f4705c97df950785394b3ac181f5bf6a08088a672 \ + --hash=sha256:6f7517a220aca8b822e25b08b0df9546701a606a328da5bc057e5f32a3f9b07c \ + --hash=sha256:713b762892efa8cd5d8dac24d16ac2d2dbf981963ed1b3297e79755f03f8cbb8 \ + --hash=sha256:72858a27dd7bd1c40f91c4f85db3b9f121c8412fd66573121febb00d074d0530 \ + --hash=sha256:778a819c2d194e08d39d67ddb15ef0d32eba17bf7d0c2773e97bd221b2613a3e \ + --hash=sha256:803606e206f3e366eea46b1e7ab4dac74cfac770d04de9c35319814e11e47c46 \ + --hash=sha256:856fcbc3201a6cabf0478daa0c0a1a8a175af7e5173e2084ddb91cc707a09dd1 \ + --hash=sha256:8f65d2a98f198e904dbe89ecb10862d5f0511367d823689039e17c4d011de11e \ + --hash=sha256:94db5ea640330de0945b41dc77fb4847b4ab6e87149126c71b36b112e8400898 \ + --hash=sha256:950e873ceefbd283cbe7bc5b648b832d1dcf89eeded6726ebec42bc7d67966c0 \ + --hash=sha256:a7beda44f177ee602aa27e0a297da1657d9572679522c8fb8b336b734653516e \ + --hash=sha256:aef0838f28328523e9e5f2c1852dd96fb85768deb0eb8f908c54dad0f44d2f6f \ + --hash=sha256:b42ea77f4e7374a67e1f27aaa9c62627dff681f67890e5b8f0c1e21b1500d9d2 \ + --hash=sha256:bccd0d604d814e9494f3bf3f077a23835580ed1743c5175581882e7dd1f178c3 \ + --hash=sha256:c2d78c8230bda5fc9f6b1d457c7f8f3432f4fe85bed86f80ba3ed73d59775a88 \ + --hash=sha256:c3529fb98a40f89269175442c5ff4ef81d22e91b2bdcbd33833a350709b5130c \ + --hash=sha256:cc8e926d6ffa65d0dddb871b7afe117f17bc045951e66afde60eb0eba923db9e \ + --hash=sha256:ce90aad0a3dc0f13a9ff0ab1f43bcbea436089b83c3fadbe37c6f1733b938bf1 \ + --hash=sha256:cec702974f162026bf8de47f6f4b7ce9584a63c50002b38f195ee797165fea77 \ + --hash=sha256:d9ef8119ce44f90d2f8ac7c58f7da480ada5151f217dc8da03681b73fc91dec3 \ + --hash=sha256:eccaefbd646022b5313ca4b0c5f1ae6e0d3a52ef66de64970ecf3f9b2a1be751 \ + --hash=sha256:fb91deb5121b6dde88599bcb3db3fdad9cf33ff3d4ccc5329ee1fe9655a2f7ff \ + --hash=sha256:fc25d59ecf23ea19571065306806a29c43c67f830f0e8a121303916ba257f484 # via -r requirements.in psycopg2-binary==2.9.1 \ --hash=sha256:0b7dae87f0b729922e06f85f667de7bf16455d411971b2043bbd9577af9d1975 \ @@ -712,8 +712,28 @@ pyparsing==2.4.7 \ --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b # via packaging -pyrsistent==0.17.3 \ - --hash=sha256:2e636185d9eb976a18a8a8e96efce62f2905fea90041958d8cc2a189756ebf3e +pyrsistent==0.18.0 \ + --hash=sha256:097b96f129dd36a8c9e33594e7ebb151b1515eb52cceb08474c10a5479e799f2 \ + --hash=sha256:2aaf19dc8ce517a8653746d98e962ef480ff34b6bc563fc067be6401ffb457c7 \ + --hash=sha256:404e1f1d254d314d55adb8d87f4f465c8693d6f902f67eb6ef5b4526dc58e6ea \ + --hash=sha256:48578680353f41dca1ca3dc48629fb77dfc745128b56fc01096b2530c13fd426 \ + --hash=sha256:4916c10896721e472ee12c95cdc2891ce5890898d2f9907b1b4ae0f53588b710 \ + --hash=sha256:527be2bfa8dc80f6f8ddd65242ba476a6c4fb4e3aedbf281dfbac1b1ed4165b1 \ + --hash=sha256:58a70d93fb79dc585b21f9d72487b929a6fe58da0754fa4cb9f279bb92369396 \ + --hash=sha256:5e4395bbf841693eaebaa5bb5c8f5cdbb1d139e07c975c682ec4e4f8126e03d2 \ + --hash=sha256:6b5eed00e597b5b5773b4ca30bd48a5774ef1e96f2a45d105db5b4ebb4bca680 \ + --hash=sha256:73ff61b1411e3fb0ba144b8f08d6749749775fe89688093e1efef9839d2dcc35 \ + --hash=sha256:772e94c2c6864f2cd2ffbe58bb3bdefbe2a32afa0acb1a77e472aac831f83427 \ + --hash=sha256:773c781216f8c2900b42a7b638d5b517bb134ae1acbebe4d1e8f1f41ea60eb4b \ + --hash=sha256:a0c772d791c38bbc77be659af29bb14c38ced151433592e326361610250c605b \ + --hash=sha256:b29b869cf58412ca5738d23691e96d8aff535e17390128a1a52717c9a109da4f \ + --hash=sha256:c1a9ff320fa699337e05edcaae79ef8c2880b52720bc031b219e5b5008ebbdef \ + --hash=sha256:cd3caef37a415fd0dae6148a1b6957a8c5f275a62cca02e18474608cb263640c \ + --hash=sha256:d5ec194c9c573aafaceebf05fc400656722793dac57f254cd4741f3c27ae57b4 \ + --hash=sha256:da6e5e818d18459fa46fac0a4a4e543507fe1110e808101277c5a2b5bab0cd2d \ + --hash=sha256:e79d94ca58fcafef6395f6352383fa1a76922268fa02caa2272fff501c2fdc78 \ + --hash=sha256:f3ef98d7b76da5eb19c37fda834d50262ff9167c65658d1d8f974d2e4d90676b \ + --hash=sha256:f4c8cabb46ff8e5d61f56a037974228e978f26bfefce4f61a4b1ac0ba7a2ab72 # via jsonschema python-dateutil==2.8.1 \ --hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \ @@ -868,9 +888,9 @@ tzlocal==2.1 \ --hash=sha256:643c97c5294aedc737780a49d9df30889321cbe1204eac2c2ec6134035a92e44 \ --hash=sha256:e2cb6c6b5b604af38597403e9852872d7f534962ae2954c7f35efcb1ccacf4a4 # via rfc5424-logging-handler -urllib3==1.26.5 \ - --hash=sha256:753a0374df26658f99d826cfe40394a686d05985786d946fbe4165b5148f5a7c \ - --hash=sha256:a7acd0977125325f516bda9735fa7142b909a8d01e8b2e4c8108d0984e6e0098 +urllib3==1.26.6 \ + --hash=sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4 \ + --hash=sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f # via # requests # selenium From 3b5dcffeac23c9dd6c91c4eab5bb3663d5d78ed4 Mon Sep 17 00:00:00 2001 From: davidpofo Date: Fri, 2 Jul 2021 13:13:38 -0400 Subject: [PATCH 26/28] check if previously checked and if so then don't hide compare button. --- templates/components/component_library.html | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/templates/components/component_library.html b/templates/components/component_library.html index 30ab60644..ea2258d3b 100644 --- a/templates/components/component_library.html +++ b/templates/components/component_library.html @@ -103,13 +103,19 @@

Component Library

{% block scripts %}