From f6c30018d8cd05654167f7d42ec577d7a8fc9085 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quirin=20Zie=C3=9Fler?= <19915467+quirinziessler@users.noreply.github.com> Date: Tue, 6 Feb 2024 10:47:33 +0100 Subject: [PATCH 01/22] WIP --- .../0200_findings_add_epss_score_field.py | 13 +++++++++++++ dojo/models.py | 5 +++++ 2 files changed, 18 insertions(+) create mode 100644 dojo/db_migrations/0200_findings_add_epss_score_field.py diff --git a/dojo/db_migrations/0200_findings_add_epss_score_field.py b/dojo/db_migrations/0200_findings_add_epss_score_field.py new file mode 100644 index 00000000000..e24e6eec969 --- /dev/null +++ b/dojo/db_migrations/0200_findings_add_epss_score_field.py @@ -0,0 +1,13 @@ +# Generated by Django 4.1.11 on 2023-11-08 20:33 + +from django.db import migrations, models + +class Migration(migrations.Migration): + + operations = [ + migrations.AddField( + model_name='finding', + name='epss_score', + field=models.FloatField(blank=True, default=None, help_text='EPSS score for the CVE. Describes how likely it is the vulnerability will be exploited in the next 30 days.', max_length=24), + ), + ] diff --git a/dojo/models.py b/dojo/models.py index 64fc8137167..a09435bbd01 100755 --- a/dojo/models.py +++ b/dojo/models.py @@ -2132,6 +2132,10 @@ class Finding(models.Model): blank=False, verbose_name=_("Vulnerability Id"), help_text=_("An id of a vulnerability in a security advisory associated with this finding. Can be a Common Vulnerabilities and Exposures (CVE) or from other sources.")) + epss_score = models.FloatField(default=0, null=True, blank=True, + verbose_name=_("EPSS"), + help_text=_("EPSS score for the CVE. Describes how likely it is the vulnerability will be exploited in the next 30 days.")) + cvssv3_regex = RegexValidator(regex=r'^AV:[NALP]|AC:[LH]|PR:[UNLH]|UI:[NR]|S:[UC]|[CIA]:[NLH]', message="CVSS must be entered in format: 'AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H'") cvssv3 = models.TextField(validators=[cvssv3_regex], max_length=117, @@ -2441,6 +2445,7 @@ class Meta: models.Index(fields=['test', 'component_name']), models.Index(fields=['cve']), + models.Index(fields=['epss']), models.Index(fields=['cwe']), models.Index(fields=['out_of_scope']), models.Index(fields=['false_p']), From d3ee50d2beb5bc2fc7862736a53e9e15faa3833a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quirin=20Zie=C3=9Fler?= Date: Tue, 6 Feb 2024 15:10:31 +0100 Subject: [PATCH 02/22] first draw --- .../0200_findings_add_epss_score_field.py | 18 +++++++++-- dojo/models.py | 8 +++-- dojo/tools/dependency_track/parser.py | 16 ++++++++++ .../scans/wazuh/one_endpoint_finding.json | 31 +++++++++++++++++++ unittests/tools/test_wazuh_parser.py | 15 +++++++++ 5 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 unittests/scans/wazuh/one_endpoint_finding.json diff --git a/dojo/db_migrations/0200_findings_add_epss_score_field.py b/dojo/db_migrations/0200_findings_add_epss_score_field.py index e24e6eec969..bd6144bbfc3 100644 --- a/dojo/db_migrations/0200_findings_add_epss_score_field.py +++ b/dojo/db_migrations/0200_findings_add_epss_score_field.py @@ -1,4 +1,3 @@ -# Generated by Django 4.1.11 on 2023-11-08 20:33 from django.db import migrations, models @@ -8,6 +7,21 @@ class Migration(migrations.Migration): migrations.AddField( model_name='finding', name='epss_score', - field=models.FloatField(blank=True, default=None, help_text='EPSS score for the CVE. Describes how likely it is the vulnerability will be exploited in the next 30 days.', max_length=24), + field=models.FloatField(blank=True, default=None, help_text='EPSS score for the CVE. Describes how likely it is the vulnerability will be exploited in the next 30 days.', max_length=6), + ), + migrations.AddField( + model_name='finding', + name='epss_percentile', + field=models.FloatField(blank=True, default=None, help_text='EPSS percentile for the CVE. Describes how many CVEs are scored at or below this one.', max_length=6), + ), + migrations.AddField( + model_name='finding_template', + name='epss_score', + field=models.FloatField(blank=True, default=None, help_text='EPSS score for the CVE. Describes how likely it is the vulnerability will be exploited in the next 30 days.', max_length=6), + ), + migrations.AddField( + model_name='finding_template', + name='epss_percentile', + field=models.FloatField(blank=True, default=None, help_text='EPSS percentile for the CVE. Describes how many CVEs are scored at or below this one.', max_length=6), ), ] diff --git a/dojo/models.py b/dojo/models.py index a09435bbd01..b2f0119a689 100755 --- a/dojo/models.py +++ b/dojo/models.py @@ -2133,8 +2133,11 @@ class Finding(models.Model): verbose_name=_("Vulnerability Id"), help_text=_("An id of a vulnerability in a security advisory associated with this finding. Can be a Common Vulnerabilities and Exposures (CVE) or from other sources.")) epss_score = models.FloatField(default=0, null=True, blank=True, - verbose_name=_("EPSS"), + verbose_name=_("EPSS Score"), help_text=_("EPSS score for the CVE. Describes how likely it is the vulnerability will be exploited in the next 30 days.")) + epss_percentile = models.FloatField(default=0, null=True, blank=True, + verbose_name=_("EPSS percentile"), + help_text=_("EPSS percentile for the CVE. Describes how many CVEs are scored at or below this one.")) cvssv3_regex = RegexValidator(regex=r'^AV:[NALP]|AC:[LH]|PR:[UNLH]|UI:[NR]|S:[UC]|[CIA]:[NLH]', message="CVSS must be entered in format: 'AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H'") cvssv3 = models.TextField(validators=[cvssv3_regex], @@ -2445,7 +2448,8 @@ class Meta: models.Index(fields=['test', 'component_name']), models.Index(fields=['cve']), - models.Index(fields=['epss']), + models.Index(fields=['epss_score']), + models.Index(fields=['epss_percentile']), models.Index(fields=['cwe']), models.Index(fields=['out_of_scope']), models.Index(fields=['false_p']), diff --git a/dojo/tools/dependency_track/parser.py b/dojo/tools/dependency_track/parser.py index 3150a3f2294..4f3a786668c 100644 --- a/dojo/tools/dependency_track/parser.py +++ b/dojo/tools/dependency_track/parser.py @@ -211,6 +211,16 @@ def _convert_dependency_track_finding_to_dojo_finding(self, dependency_track_fin analysis = dependency_track_finding.get('analysis') is_false_positive = True if analysis is not None and analysis.get('state') == 'FALSE_POSITIVE' else False + # Get the EPSS details + if 'epssPercentile' in dependency_track_finding['vulnerability']: + epss_percentile = dependency_track_finding['vulnerability']['epssPercentile'] + else: + epss_percentile = None + + if 'epssScore' in dependency_track_finding['vulnerability']: + epss_score = dependency_track_finding['vulnerability']['epssScore'] + + # Build and return Finding model finding = Finding( title=title, @@ -235,6 +245,12 @@ def _convert_dependency_track_finding_to_dojo_finding(self, dependency_track_fin if cvss_score: finding.cvssv3_score = cvss_score + + if epss_score: + finding.epss_score = epss_score + + if epss_percentile: + finding.epss_percentile = epss_percentile return finding diff --git a/unittests/scans/wazuh/one_endpoint_finding.json b/unittests/scans/wazuh/one_endpoint_finding.json new file mode 100644 index 00000000000..a3ab190a4b4 --- /dev/null +++ b/unittests/scans/wazuh/one_endpoint_finding.json @@ -0,0 +1,31 @@ +{ + "data": { + "affected_items": [ + { + "architecture": "amd64", + "condition": "Package less than 4.3.2", + "cve": "CVE-1234-123123", + "cvss2_score": 0, + "cvss3_score": 5.5, + "detection_time": "2023-02-08T13:55:10Z", + "external_references": [ + "https://nvd.nist.gov/vuln/detail/CVE-YYYY-XXXXX", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-YYYY-XXXXX" + ], + "name": "asdf", + "published": "2022-09-01", + "severity": "Medium", + "status": "VALID", + "title": "CVE-YYYY-XXXXX affects asdf", + "type": "PACKAGE", + "updated": "2022-09-07", + "version": "4.3.1" + } + ], + "failed_items": [], + "total_affected_items": 1, + "total_failed_items": 0 + }, + "error": 0, + "message": "All selected vulnerabilities were returned" +} \ No newline at end of file diff --git a/unittests/tools/test_wazuh_parser.py b/unittests/tools/test_wazuh_parser.py index 51c026304c4..15baa1102dc 100644 --- a/unittests/tools/test_wazuh_parser.py +++ b/unittests/tools/test_wazuh_parser.py @@ -21,6 +21,21 @@ def test_parse_one_finding(self): self.assertEqual(1, len(findings)) self.assertEqual("Medium", finding.severity) self.assertEqual("CVE-1234-123123", finding.unsaved_vulnerability_ids[0]) + self.assertEqual("CVE-YYYY-XXXXX affects asdf (version: 4.3.1)", finding.title) + self.assertEqual("https://nvd.nist.gov/vuln/detail/CVE-YYYY-XXXXX\nhttps://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-YYYY-XXXXX", finding.references) + self.assertEqual("asdf", finding.component_name) + + def test_parse_one_endpoint_finding(self): + testfile = open("unittests/scans/wazuh/one_endpoint_finding.json") + parser = WazuhParser() + findings = parser.get_findings(testfile, Test()) + for finding in findings: + for endpoint in finding.unsaved_endpoints: + endpoint.clean() + self.assertEqual(1, len(findings)) + self.assertEqual("Medium", finding.severity) + self.assertEqual("CVE-1234-123123", finding.unsaved_vulnerability_ids[0]) + self.assertEqual("123.123.123.123", finding.unsaved_endpoints) def test_parse_many_finding(self): testfile = open("unittests/scans/wazuh/many_findings.json") From 1f6f7ec03b5a3030e08210b2c7e6af9a87967a16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quirin=20Zie=C3=9Fler?= Date: Tue, 6 Feb 2024 15:22:50 +0100 Subject: [PATCH 03/22] fix migrations --- dojo/db_migrations/0200_findings_add_epss_score_field.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dojo/db_migrations/0200_findings_add_epss_score_field.py b/dojo/db_migrations/0200_findings_add_epss_score_field.py index bd6144bbfc3..1450393d9aa 100644 --- a/dojo/db_migrations/0200_findings_add_epss_score_field.py +++ b/dojo/db_migrations/0200_findings_add_epss_score_field.py @@ -3,6 +3,10 @@ class Migration(migrations.Migration): + dependencies = [ + ('dojo', '0199_whitesource_to_mend'), + ] + operations = [ migrations.AddField( model_name='finding', From 8a50a289e825dccc8ca8b593c41b4218927c64e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quirin=20Zie=C3=9Fler?= <19915467+quirinziessler@users.noreply.github.com> Date: Tue, 6 Feb 2024 15:30:29 +0100 Subject: [PATCH 04/22] fix migrations --- ...s_score_and_epps_percentile_to_findings.py | 31 +++++++++++++++++++ .../0200_findings_add_epss_score_field.py | 31 ------------------- 2 files changed, 31 insertions(+), 31 deletions(-) create mode 100644 dojo/db_migrations/0200_add_epss_score_and_epps_percentile_to_findings.py delete mode 100644 dojo/db_migrations/0200_findings_add_epss_score_field.py diff --git a/dojo/db_migrations/0200_add_epss_score_and_epps_percentile_to_findings.py b/dojo/db_migrations/0200_add_epss_score_and_epps_percentile_to_findings.py new file mode 100644 index 00000000000..8f600f7e418 --- /dev/null +++ b/dojo/db_migrations/0200_add_epss_score_and_epps_percentile_to_findings.py @@ -0,0 +1,31 @@ +# Generated by Django 4.1.13 on 2024-02-06 14:28 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dojo', '0199_whitesource_to_mend'), + ] + + operations = [ + migrations.AddField( + model_name='finding', + name='epss_percentile', + field=models.FloatField(blank=True, default=0, help_text='EPSS percentile for the CVE. Describes how many CVEs are scored at or below this one.', null=True, verbose_name='EPSS percentile'), + ), + migrations.AddField( + model_name='finding', + name='epss_score', + field=models.FloatField(blank=True, default=0, help_text='EPSS score for the CVE. Describes how likely it is the vulnerability will be exploited in the next 30 days.', null=True, verbose_name='EPSS Score'), + ), + migrations.AddIndex( + model_name='finding', + index=models.Index(fields=['epss_score'], name='dojo_findin_epss_sc_e40540_idx'), + ), + migrations.AddIndex( + model_name='finding', + index=models.Index(fields=['epss_percentile'], name='dojo_findin_epss_pe_567499_idx'), + ), + ] \ No newline at end of file diff --git a/dojo/db_migrations/0200_findings_add_epss_score_field.py b/dojo/db_migrations/0200_findings_add_epss_score_field.py deleted file mode 100644 index 1450393d9aa..00000000000 --- a/dojo/db_migrations/0200_findings_add_epss_score_field.py +++ /dev/null @@ -1,31 +0,0 @@ - -from django.db import migrations, models - -class Migration(migrations.Migration): - - dependencies = [ - ('dojo', '0199_whitesource_to_mend'), - ] - - operations = [ - migrations.AddField( - model_name='finding', - name='epss_score', - field=models.FloatField(blank=True, default=None, help_text='EPSS score for the CVE. Describes how likely it is the vulnerability will be exploited in the next 30 days.', max_length=6), - ), - migrations.AddField( - model_name='finding', - name='epss_percentile', - field=models.FloatField(blank=True, default=None, help_text='EPSS percentile for the CVE. Describes how many CVEs are scored at or below this one.', max_length=6), - ), - migrations.AddField( - model_name='finding_template', - name='epss_score', - field=models.FloatField(blank=True, default=None, help_text='EPSS score for the CVE. Describes how likely it is the vulnerability will be exploited in the next 30 days.', max_length=6), - ), - migrations.AddField( - model_name='finding_template', - name='epss_percentile', - field=models.FloatField(blank=True, default=None, help_text='EPSS percentile for the CVE. Describes how many CVEs are scored at or below this one.', max_length=6), - ), - ] From f877c95cda8b0d1b981ec2c8d0fd617dd35a3dc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quirin=20Zie=C3=9Fler?= Date: Tue, 6 Feb 2024 16:31:47 +0100 Subject: [PATCH 05/22] add epss to findings UI --- dojo/templates/dojo/view_finding.html | 23 +++++++++++++++++++++++ dojo/tools/dependency_track/parser.py | 2 ++ 2 files changed, 25 insertions(+) diff --git a/dojo/templates/dojo/view_finding.html b/dojo/templates/dojo/view_finding.html index 5a49cbd5bbf..94a3bb38635 100755 --- a/dojo/templates/dojo/view_finding.html +++ b/dojo/templates/dojo/view_finding.html @@ -440,6 +440,29 @@

+ {% if finding.epss_score or finding.epss_percentile %} +
+ + + {% if finding.epss_score%} + + {% endif %} + {% if finding.epss_percentile%} + + {% endif %} + + + {% if finding.epss_score%} + + {% endif %} + {% if finding.epss_percentile%} + + {% endif %} + +
EPSS ScoreEPSS Percentile
{{ finding.epss_score }}{{ finding.epss_percentile }}
+
+ {% endif %} + {% with finding|additional_vulnerability_ids as additional_vulnerability_ids %} {% if additional_vulnerability_ids %}
diff --git a/dojo/tools/dependency_track/parser.py b/dojo/tools/dependency_track/parser.py index 4f3a786668c..1af1e8422e9 100644 --- a/dojo/tools/dependency_track/parser.py +++ b/dojo/tools/dependency_track/parser.py @@ -219,6 +219,8 @@ def _convert_dependency_track_finding_to_dojo_finding(self, dependency_track_fin if 'epssScore' in dependency_track_finding['vulnerability']: epss_score = dependency_track_finding['vulnerability']['epssScore'] + else: + epss_score = None # Build and return Finding model From c2aae2e343ea113fd7deb8f8c53ca7c71075a535 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quirin=20Zie=C3=9Fler?= Date: Fri, 9 Feb 2024 14:40:34 +0100 Subject: [PATCH 06/22] added epss to finding list --- dojo/filters.py | 9 ++++++++- dojo/models.py | 2 +- .../templates/dojo/findings_list_snippet.html | 20 +++++++++++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/dojo/filters.py b/dojo/filters.py index 20db8bddcfd..e1cc349d64b 100644 --- a/dojo/filters.py +++ b/dojo/filters.py @@ -325,6 +325,8 @@ def get_finding_filterset_fields(metrics=False, similar=False): 'unique_id_from_tool', 'vuln_id_from_tool', 'service', + 'epss_score', + 'epss_percentile' ]) if similar: @@ -1446,6 +1448,8 @@ class FindingFilter(FindingFilterWithTags): ('test__engagement__product__name', 'test__engagement__product__name'), ('service', 'service'), + ('epss_score', 'epss_score'), + ('epss_percentile', 'epss_percentile'), ), field_labels={ 'numerical_severity': 'Severity', @@ -1454,6 +1458,8 @@ class FindingFilter(FindingFilterWithTags): 'mitigated': 'Mitigated Date', 'title': 'Finding Name', 'test__engagement__product__name': 'Product Name', + 'epss_score': 'EPSS Score', + 'epss_percentile': 'EPSS Percentile', } ) @@ -1468,7 +1474,8 @@ class Meta: 'hash_code', 'reviewers', 'created', 'files', 'sla_start_date', 'cvssv3', - 'severity_justification', 'steps_to_reproduce'] + 'severity_justification', 'steps_to_reproduce', + 'epss_score', 'epss_percentile'] def __init__(self, *args, **kwargs): self.user = None diff --git a/dojo/models.py b/dojo/models.py index b2f0119a689..8ef130a1091 100755 --- a/dojo/models.py +++ b/dojo/models.py @@ -2433,7 +2433,7 @@ class Finding(models.Model): 'High': 1, 'Critical': 0} class Meta: - ordering = ('numerical_severity', '-date', 'title') + ordering = ('numerical_severity', '-date', 'title', 'epss_score', 'epss_percentile') indexes = [ models.Index(fields=['test', 'active', 'verified']), diff --git a/dojo/templates/dojo/findings_list_snippet.html b/dojo/templates/dojo/findings_list_snippet.html index 248b7491942..95abfa7c489 100644 --- a/dojo/templates/dojo/findings_list_snippet.html +++ b/dojo/templates/dojo/findings_list_snippet.html @@ -322,6 +322,12 @@

{% trans "Vulnerability Id" %} + + {% trans "EPSS Score" %} + + + {% trans "EPSS Percentile" %} + {% if filter_name == 'Closed' %} {% comment %} The display field is translated in the function. No need to translate here as well{% endcomment %} @@ -593,6 +599,20 @@

{% endif %} {% endwith %} + + {% if finding.epss_score %} + {{ finding.epss_score }} + {% else %} + N.A. + {% endif %} + + + {% if finding.epss_percentile %} + {{ finding.epss_percentile }} + {% else %} + N.A. + {% endif %} + {% if filter_name == 'Closed' %} {{ finding.mitigated|date }} From 8edf63d64a3d72b43121cf985cf2e8b24821402d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quirin=20Hardy=20Zie=C3=9Fler?= Date: Fri, 9 Feb 2024 14:58:09 +0100 Subject: [PATCH 07/22] Delete unittests/scans/wazuh/one_endpoint_finding.json --- .../scans/wazuh/one_endpoint_finding.json | 31 ------------------- 1 file changed, 31 deletions(-) delete mode 100644 unittests/scans/wazuh/one_endpoint_finding.json diff --git a/unittests/scans/wazuh/one_endpoint_finding.json b/unittests/scans/wazuh/one_endpoint_finding.json deleted file mode 100644 index a3ab190a4b4..00000000000 --- a/unittests/scans/wazuh/one_endpoint_finding.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "data": { - "affected_items": [ - { - "architecture": "amd64", - "condition": "Package less than 4.3.2", - "cve": "CVE-1234-123123", - "cvss2_score": 0, - "cvss3_score": 5.5, - "detection_time": "2023-02-08T13:55:10Z", - "external_references": [ - "https://nvd.nist.gov/vuln/detail/CVE-YYYY-XXXXX", - "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-YYYY-XXXXX" - ], - "name": "asdf", - "published": "2022-09-01", - "severity": "Medium", - "status": "VALID", - "title": "CVE-YYYY-XXXXX affects asdf", - "type": "PACKAGE", - "updated": "2022-09-07", - "version": "4.3.1" - } - ], - "failed_items": [], - "total_affected_items": 1, - "total_failed_items": 0 - }, - "error": 0, - "message": "All selected vulnerabilities were returned" -} \ No newline at end of file From cfad534e16db697211ddb46c551925cc94d7679c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quirin=20Zie=C3=9Fler?= Date: Fri, 9 Feb 2024 15:38:38 +0100 Subject: [PATCH 08/22] flake8 --- .../0200_add_epss_score_and_epps_percentile_to_findings.py | 2 +- dojo/models.py | 1 - dojo/tools/dependency_track/parser.py | 4 +--- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/dojo/db_migrations/0200_add_epss_score_and_epps_percentile_to_findings.py b/dojo/db_migrations/0200_add_epss_score_and_epps_percentile_to_findings.py index 8f600f7e418..9fa542f6afa 100644 --- a/dojo/db_migrations/0200_add_epss_score_and_epps_percentile_to_findings.py +++ b/dojo/db_migrations/0200_add_epss_score_and_epps_percentile_to_findings.py @@ -28,4 +28,4 @@ class Migration(migrations.Migration): model_name='finding', index=models.Index(fields=['epss_percentile'], name='dojo_findin_epss_pe_567499_idx'), ), - ] \ No newline at end of file + ] diff --git a/dojo/models.py b/dojo/models.py index 8ef130a1091..7f869d66b04 100755 --- a/dojo/models.py +++ b/dojo/models.py @@ -2138,7 +2138,6 @@ class Finding(models.Model): epss_percentile = models.FloatField(default=0, null=True, blank=True, verbose_name=_("EPSS percentile"), help_text=_("EPSS percentile for the CVE. Describes how many CVEs are scored at or below this one.")) - cvssv3_regex = RegexValidator(regex=r'^AV:[NALP]|AC:[LH]|PR:[UNLH]|UI:[NR]|S:[UC]|[CIA]:[NLH]', message="CVSS must be entered in format: 'AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H'") cvssv3 = models.TextField(validators=[cvssv3_regex], max_length=117, diff --git a/dojo/tools/dependency_track/parser.py b/dojo/tools/dependency_track/parser.py index 1af1e8422e9..c564268e2fb 100644 --- a/dojo/tools/dependency_track/parser.py +++ b/dojo/tools/dependency_track/parser.py @@ -222,7 +222,6 @@ def _convert_dependency_track_finding_to_dojo_finding(self, dependency_track_fin else: epss_score = None - # Build and return Finding model finding = Finding( title=title, @@ -247,10 +246,9 @@ def _convert_dependency_track_finding_to_dojo_finding(self, dependency_track_fin if cvss_score: finding.cvssv3_score = cvss_score - + if epss_score: finding.epss_score = epss_score - if epss_percentile: finding.epss_percentile = epss_percentile From 1a6c750d1d4a13eab602d2fe07759cb137538fad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quirin=20Zie=C3=9Fler?= <19915467+quirinziessler@users.noreply.github.com> Date: Sat, 10 Feb 2024 00:56:33 +0100 Subject: [PATCH 09/22] add migration for ModelOptions --- ...0200_add_epss_score_and_epps_percentile_to_findings.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/dojo/db_migrations/0200_add_epss_score_and_epps_percentile_to_findings.py b/dojo/db_migrations/0200_add_epss_score_and_epps_percentile_to_findings.py index 9fa542f6afa..fda651aee0f 100644 --- a/dojo/db_migrations/0200_add_epss_score_and_epps_percentile_to_findings.py +++ b/dojo/db_migrations/0200_add_epss_score_and_epps_percentile_to_findings.py @@ -10,15 +10,19 @@ class Migration(migrations.Migration): ] operations = [ + migrations.AlterModelOptions( + name='finding', + options={'ordering': ('numerical_severity', '-date', 'title', 'epss_score', 'epss_percentile')}, + ), migrations.AddField( model_name='finding', name='epss_percentile', - field=models.FloatField(blank=True, default=0, help_text='EPSS percentile for the CVE. Describes how many CVEs are scored at or below this one.', null=True, verbose_name='EPSS percentile'), + field=models.FloatField(blank=True, default=None, help_text='EPSS percentile for the CVE. Describes how many CVEs are scored at or below this one.', null=True, verbose_name='EPSS percentile'), ), migrations.AddField( model_name='finding', name='epss_score', - field=models.FloatField(blank=True, default=0, help_text='EPSS score for the CVE. Describes how likely it is the vulnerability will be exploited in the next 30 days.', null=True, verbose_name='EPSS Score'), + field=models.FloatField(blank=True, default=None, help_text='EPSS score for the CVE. Describes how likely it is the vulnerability will be exploited in the next 30 days.', null=True, verbose_name='EPSS Score'), ), migrations.AddIndex( model_name='finding', From b41754522b0e31b398dff66a31cc6f67fa16a65f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quirin=20Zie=C3=9Fler?= <19915467+quirinziessler@users.noreply.github.com> Date: Sat, 10 Feb 2024 00:57:28 +0100 Subject: [PATCH 10/22] Add null values for epss + validators --- dojo/models.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/dojo/models.py b/dojo/models.py index 7f869d66b04..8b4d82210ec 100755 --- a/dojo/models.py +++ b/dojo/models.py @@ -13,7 +13,7 @@ from django.contrib.auth.models import Group from django.db.models.expressions import Case, When from django.urls import reverse -from django.core.validators import RegexValidator, validate_ipv46_address +from django.core.validators import RegexValidator, validate_ipv46_address, MinValueValidator, MaxValueValidator from django.core.files.base import ContentFile from django.core.exceptions import ValidationError from django.db import models, connection @@ -2132,12 +2132,14 @@ class Finding(models.Model): blank=False, verbose_name=_("Vulnerability Id"), help_text=_("An id of a vulnerability in a security advisory associated with this finding. Can be a Common Vulnerabilities and Exposures (CVE) or from other sources.")) - epss_score = models.FloatField(default=0, null=True, blank=True, + epss_score = models.FloatField(default=None, null=True, blank=True, verbose_name=_("EPSS Score"), - help_text=_("EPSS score for the CVE. Describes how likely it is the vulnerability will be exploited in the next 30 days.")) - epss_percentile = models.FloatField(default=0, null=True, blank=True, + help_text=_("EPSS score for the CVE. Describes how likely it is the vulnerability will be exploited in the next 30 days."), + validators=[MinValueValidator(0.0), MaxValueValidator(1.0)]) + epss_percentile = models.FloatField(default=None, null=True, blank=True, verbose_name=_("EPSS percentile"), - help_text=_("EPSS percentile for the CVE. Describes how many CVEs are scored at or below this one.")) + help_text=_("EPSS percentile for the CVE. Describes how many CVEs are scored at or below this one."), + validators=[MinValueValidator(0.0), MaxValueValidator(1.0)]) cvssv3_regex = RegexValidator(regex=r'^AV:[NALP]|AC:[LH]|PR:[UNLH]|UI:[NR]|S:[UC]|[CIA]:[NLH]', message="CVSS must be entered in format: 'AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H'") cvssv3 = models.TextField(validators=[cvssv3_regex], max_length=117, From 7708fbaefca1561aa01a6b3d857dca595558b1f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quirin=20Zie=C3=9Fler?= <19915467+quirinziessler@users.noreply.github.com> Date: Sat, 10 Feb 2024 11:31:46 +0100 Subject: [PATCH 11/22] updated findings detail page to display epss as percentage --- dojo/templates/dojo/view_finding.html | 48 ++++++++++++--------------- dojo/templatetags/multiply.py | 7 ++++ 2 files changed, 29 insertions(+), 26 deletions(-) create mode 100644 dojo/templatetags/multiply.py diff --git a/dojo/templates/dojo/view_finding.html b/dojo/templates/dojo/view_finding.html index 94a3bb38635..d8fec067ecb 100755 --- a/dojo/templates/dojo/view_finding.html +++ b/dojo/templates/dojo/view_finding.html @@ -1,5 +1,6 @@ {% extends "base.html" %} {% load display_tags %} +{% load multiply %} {% load authorization_tags %} {% load humanize %} {% load static %} @@ -268,8 +269,17 @@

Date Mitigated Mitigated By {% endif %} - CWE - Vulnerability Id + CWE + Vulnerability Id + {% if finding.epss_score != None or finding.epss_percentile != None %} + {% if finding.epss_score != None and finding.epss_percentile != None %} + EPSS Score / EPSS Percentile + {% elif finding.epss_score != None and finding.epss_percentile == None %} + EPSS Score + {% elif finding.epss_score == None and finding.epss_percentile != None %} + EPSS Percentile + {% endif %} + {% endif %} Found by {% if finding.vuln_id_from_tool %} Vuln ID from tool @@ -421,7 +431,16 @@

{% endif %} {% endif %} - + {% if finding.epss_score != None or finding.epss_percentile != None %} + {% if finding.epss_score != None and finding.epss_percentile != None %} + {{ finding.epss_score|multiply:100|floatformat:"3" }}% / {{ finding.epss_percentile|multiply:100|floatformat:"3" }}% + {% elif finding.epss_score != None and finding.epss_percentile == None %} + {{ finding.epss_score|multiply:100|floatformat:"3" }}% + {% elif finding.epss_score == None and finding.epss_percentile != None %} + {{ finding.epss_percentile|multiply:100|floatformat:"3" }}% + {% endif %} + {% endif %} + {% if found_by %} {% for scanner in found_by %} {{ scanner }} @@ -440,29 +459,6 @@

- {% if finding.epss_score or finding.epss_percentile %} -
- - - {% if finding.epss_score%} - - {% endif %} - {% if finding.epss_percentile%} - - {% endif %} - - - {% if finding.epss_score%} - - {% endif %} - {% if finding.epss_percentile%} - - {% endif %} - -
EPSS ScoreEPSS Percentile
{{ finding.epss_score }}{{ finding.epss_percentile }}
-
- {% endif %} - {% with finding|additional_vulnerability_ids as additional_vulnerability_ids %} {% if additional_vulnerability_ids %}
diff --git a/dojo/templatetags/multiply.py b/dojo/templatetags/multiply.py new file mode 100644 index 00000000000..fc8b8b24aa1 --- /dev/null +++ b/dojo/templatetags/multiply.py @@ -0,0 +1,7 @@ +from django import template + +register = template.Library() + +@register.filter +def multiply(value, arg): + return value * arg \ No newline at end of file From e2e0255e9d91950e5e162cf599409037f5e314fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quirin=20Zie=C3=9Fler?= <19915467+quirinziessler@users.noreply.github.com> Date: Sat, 10 Feb 2024 11:33:35 +0100 Subject: [PATCH 12/22] removed wazuh file --- unittests/tools/test_wazuh_parser.py | 47 ---------------------------- 1 file changed, 47 deletions(-) delete mode 100644 unittests/tools/test_wazuh_parser.py diff --git a/unittests/tools/test_wazuh_parser.py b/unittests/tools/test_wazuh_parser.py deleted file mode 100644 index 15baa1102dc..00000000000 --- a/unittests/tools/test_wazuh_parser.py +++ /dev/null @@ -1,47 +0,0 @@ -from ..dojo_test_case import DojoTestCase -from dojo.tools.wazuh.parser import WazuhParser -from dojo.models import Test - - -class TestWazuhParser(DojoTestCase): - - def test_parse_no_findings(self): - testfile = open("unittests/scans/wazuh/no_findings.json") - parser = WazuhParser() - findings = parser.get_findings(testfile, Test()) - self.assertEqual(0, len(findings)) - - def test_parse_one_finding(self): - testfile = open("unittests/scans/wazuh/one_finding.json") - parser = WazuhParser() - findings = parser.get_findings(testfile, Test()) - for finding in findings: - for endpoint in finding.unsaved_endpoints: - endpoint.clean() - self.assertEqual(1, len(findings)) - self.assertEqual("Medium", finding.severity) - self.assertEqual("CVE-1234-123123", finding.unsaved_vulnerability_ids[0]) - self.assertEqual("CVE-YYYY-XXXXX affects asdf (version: 4.3.1)", finding.title) - self.assertEqual("https://nvd.nist.gov/vuln/detail/CVE-YYYY-XXXXX\nhttps://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-YYYY-XXXXX", finding.references) - self.assertEqual("asdf", finding.component_name) - - def test_parse_one_endpoint_finding(self): - testfile = open("unittests/scans/wazuh/one_endpoint_finding.json") - parser = WazuhParser() - findings = parser.get_findings(testfile, Test()) - for finding in findings: - for endpoint in finding.unsaved_endpoints: - endpoint.clean() - self.assertEqual(1, len(findings)) - self.assertEqual("Medium", finding.severity) - self.assertEqual("CVE-1234-123123", finding.unsaved_vulnerability_ids[0]) - self.assertEqual("123.123.123.123", finding.unsaved_endpoints) - - def test_parse_many_finding(self): - testfile = open("unittests/scans/wazuh/many_findings.json") - parser = WazuhParser() - findings = parser.get_findings(testfile, Test()) - for finding in findings: - for endpoint in finding.unsaved_endpoints: - endpoint.clean() - self.assertEqual(6, len(findings)) From f51330d9a8087dc01259e9790ef299729a96153d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quirin=20Hardy=20Zie=C3=9Fler?= Date: Sun, 11 Feb 2024 00:04:14 +0100 Subject: [PATCH 13/22] update branch (#3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update versions in application files * Update jira-description.tpl (#9403) * Update and rename whitesource.md to mend.md (#9348) * Update and rename whitesource.md to mend.md * Update docs/content/en/integrations/parsers/file/mend.md Co-authored-by: Charles Neill <1749665+cneill@users.noreply.github.com> --------- Co-authored-by: Charles Neill <1749665+cneill@users.noreply.github.com> * API: Remote v2 OpenAPI2 Docs from menu (#9469) * :bug: fix migration (#9467) * finding sla expiration date field (part one) (#9473) * addition of sla expiration date field on the finding model * add migration and fix indentation issue * fix mitigated finding remaining sla days calculation * fix sla violation filter to return only active, sla violating findings * migration system settings fix * fix mitigation date vs datetime discrepancy * fix breaking unit test * move product save check to signal * fix unit test failure * make signal operations async, fix sla config delete 500 error * add unit tests to test sla expiration date functionality * restarting without signals * add async updating flags, redo migration * move signal logic to overriden save * fix errors for non-existing objects at creation * clean up comments and a few logical expressions * fix flake8 error * addition of new unit tests * fix unit test error * add message to form fields when async updating flag is true * fix save location, reword form messages, reword redirect messages * remove commented lines from unit tests * add a bit more description to API validation errors * migration fix * migration performance improvements * fix datetime - str comparison issue * clean up for part one of sla expiration date field * fix flake8 * Update dojo/db_migrations/0200_finding_sla_expiration_date_product_async_updating_and_more.py Co-authored-by: Charles Neill <1749665+cneill@users.noreply.github.com> * Update dojo/models.py Co-authored-by: Charles Neill <1749665+cneill@users.noreply.github.com> --------- Co-authored-by: Charles Neill <1749665+cneill@users.noreply.github.com> * Update versions in application files * Update versions in application files * Update release-drafter/release-drafter action from v5.25.0 to v6 (.github/workflows/release-drafter.yml) (#9460) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Bump pytz from 2023.4 to 2024.1 (#9465) Bumps [pytz](https://github.com/stub42/pytz) from 2023.4 to 2024.1. - [Release notes](https://github.com/stub42/pytz/releases) - [Commits](https://github.com/stub42/pytz/compare/release_2023.4...release_2024.1) --- updated-dependencies: - dependency-name: pytz dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump django-debug-toolbar from 4.2.0 to 4.3.0 (#9466) Bumps [django-debug-toolbar](https://github.com/jazzband/django-debug-toolbar) from 4.2.0 to 4.3.0. - [Release notes](https://github.com/jazzband/django-debug-toolbar/releases) - [Changelog](https://github.com/jazzband/django-debug-toolbar/blob/main/docs/changes.rst) - [Commits](https://github.com/jazzband/django-debug-toolbar/compare/4.2...4.3) --- updated-dependencies: - dependency-name: django-debug-toolbar dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump nginx from `d12e6f7` to `f2802c2` (#9477) Bumps nginx from `d12e6f7` to `f2802c2`. --- updated-dependencies: - dependency-name: nginx dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update dependency postcss from 8.4.33 to v8.4.34 (docs/package.json) (#9481) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update rabbitmq:3.12.12-alpine Docker digest from 3.12.12 to 3.12.12-alpine (docker-compose.yml) (#9458) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * :arrow_up: Bump boto3 from 1.34.32 to 1.34.35 (#9489) Bumps [boto3](https://github.com/boto/boto3) from 1.34.32 to 1.34.35. - [Release notes](https://github.com/boto/boto3/releases) - [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst) - [Commits](https://github.com/boto/boto3/compare/1.34.32...1.34.35) --- updated-dependencies: - dependency-name: boto3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update dependency ruff from 0.1.15 to v0.2.1 (requirements-lint.txt) (#9459) * Update dependency ruff from 0.1.15 to v0.2.1 (requirements-lint.txt) * Fix ruff warning (#9461) * Update dependency ruff from 0.1.15 to v0.2.0 (requirements-lint.txt) * fix ruff warning --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Charles Neill <1749665+cneill@users.noreply.github.com> --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: kiblik Co-authored-by: Charles Neill <1749665+cneill@users.noreply.github.com> * :bug: fix defaulting severity, see last comments in #8778 (#9370) Co-authored-by: Cody Maffucci <46459665+Maffooch@users.noreply.github.com> * Add ruff for *tests (#9406) * Revert ":bug: fix dependencytrack deduplication (#9117)" (#9371) This reverts commit 0f55a7f2c2db4b39ab30b868b2090c45ed9038b4. Co-authored-by: Cody Maffucci <46459665+Maffooch@users.noreply.github.com> * dojo/importers/importer/importer.py - Change "None" string to "Info" from cvss module when a CVSS vector string should evaluate to "Info" (#9453) * dojo/importers/importer/importer.py - Change "None" string to "Info" from cvss module when a CVSS vector string evaluates to "Info" * dojo/importers/importer/importer.py - Change "None" string to "Info" from cvss module when a CVSS vector string evaluates to "Info" #flake8_fix * Trivy Operator VulnerabilityReport Parser tweaks (#9452) * API: Check missing endpoints (#7618) * Rename unittest * Define exceptions for now * Announcement was implemented * Fix unittests with assertRaises + replace assertTrue/False with better checks (#9435) * Fix unittests with assertRaises * Replace assertTrue/False with better checks * Fixes * Optimize list of Maintenance in relase notes (#9492) * fix typo in docs (#9487) * :bug: WFuzz: Add additional severity mappings (#9486) * :bug: fix wfuzz, issue #7863 * add 302 * update docs * Be strict about Warnings during testing (#9490) * Set PYTHONWARNINGS=error * Add basic filterwarnings * Mute some warnings * Mute one more warning * :bug: fix trufflehog3, issue #6999 (#9470) * :bug: fix yarn_audit, #6495 (#9478) * Bump vulners from 2.1.2 to 2.1.5 (#9391) Bumps [vulners]() from 2.1.2 to 2.1.5. --- updated-dependencies: - dependency-name: vulners dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Add support for DD_APPEND_SLASH (#9385) * Override default Django APPEND_SLASH * Update dojo/settings/settings.dist.py * :tada: Improvements for wazuh importer (#9248) * improvement for wazuh importer * :wrench: change on dedupe for Wazuh * :wrench: change on dedupe for Wazuh * :memo: * :pencil2: * :memo: * :memo: * flake8 * :tada: recoded wazuh importer to support endpoints * :white_check_mark: adjusted unittests * :memo: * :pencil2: * :pencil2: --------- Co-authored-by: Cody Maffucci <46459665+Maffooch@users.noreply.github.com> * Update rabbitmq:3.12.12-alpine Docker digest from 3.12.12 to 3.12.12-alpine (docker-compose.yml) (#9501) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update dependency postcss from 8.4.34 to v8.4.35 (docs/package.json) (#9502) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Improve API endpoints for Risk Acceptances (#9415) * Modifying Bugcrowd API Parser to align to vendor documentation on wha… (#9517) * Modifying Bugcrowd API Parser to align to vendor documentation on what the not_applicable state means. It is now active == False and severity == 'Info'. [sc-4217] * fixing Flake8 errors * fixing Flake8 errors, part deux --------- Signed-off-by: dependabot[bot] Co-authored-by: DefectDojo release bot Co-authored-by: Cody Maffucci <46459665+Maffooch@users.noreply.github.com> Co-authored-by: Paul Osinski <42211303+paulOsinski@users.noreply.github.com> Co-authored-by: Charles Neill <1749665+cneill@users.noreply.github.com> Co-authored-by: kiblik Co-authored-by: manuelsommer <47991713+manuel-sommer@users.noreply.github.com> Co-authored-by: Blake Owens <76979297+blakeaowens@users.noreply.github.com> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Robert Kiss Co-authored-by: ninp0 Co-authored-by: Raouf HADDADA <22875897+raouf-haddada@users.noreply.github.com> Co-authored-by: Felix Hernandez Co-authored-by: Jay Paz --- .github/release-drafter.yml | 9 +- .github/workflows/release-drafter.yml | 2 +- .github/workflows/ruff.yml | 2 +- Dockerfile.nginx-alpine | 2 +- Dockerfile.nginx-debian | 2 +- components/package.json | 2 +- docker-compose.yml | 2 +- docker/entrypoint-integration-tests.sh | 3 + docker/entrypoint-unit-tests-devDocker.sh | 3 + docker/entrypoint-unit-tests.sh | 3 + .../en/getting_started/upgrading/2.32.md | 7 + .../integrations/parsers/file/hcl_appscan.md | 2 +- .../en/integrations/parsers/file/mend.md | 2 +- .../en/integrations/parsers/file/wazuh.md | 46 +++- .../en/integrations/parsers/file/wfuzz.md | 2 + docs/package-lock.json | 14 +- docs/package.json | 2 +- dojo/__init__.py | 2 +- dojo/api_v2/serializers.py | 38 +++- dojo/api_v2/views.py | 7 +- dojo/apps.py | 1 + dojo/db_migrations/0197_parser_merge.py | 2 +- ...on_date_product_async_updating_and_more.py | 31 +++ dojo/filters.py | 21 +- dojo/forms.py | 40 +++- dojo/importers/importer/importer.py | 19 +- dojo/models.py | 146 ++++++++++-- dojo/product/helpers.py | 26 ++- dojo/product/views.py | 7 +- dojo/settings/settings.dist.py | 18 +- dojo/sla_config/helpers.py | 26 +++ dojo/sla_config/views.py | 29 ++- dojo/templates/base.html | 6 - dojo/templates/dojo/form_fields.html | 3 +- .../jira_full/jira-description.tpl | 2 +- dojo/templatetags/display_tags.py | 2 +- dojo/tools/api_bugcrowd/parser.py | 32 ++- dojo/tools/dependency_track/parser.py | 4 +- dojo/tools/trivy_operator/parser.py | 32 +++ dojo/tools/trufflehog3/parser.py | 2 +- dojo/tools/wazuh/parser.py | 82 +++---- dojo/tools/wfuzz/parser.py | 6 +- dojo/tools/yarn_audit/parser.py | 213 ++++++++++++------ dojo/utils.py | 2 +- helm/defectdojo/Chart.yaml | 4 +- pyproject.toml | 12 +- requirements-lint.txt | 2 +- requirements.txt | 8 +- tests/Import_scanner_test.py | 2 +- tests/base_test_class.py | 8 + tests/close_old_findings_dedupe_test.py | 3 +- tests/close_old_findings_test.py | 2 +- tests/dedupe_test.py | 3 +- tests/false_positive_history_test.py | 2 - tests/finding_test.py | 4 +- tests/report_builder_test.py | 2 +- tests/search_test.py | 3 - tests/zap.py | 2 +- .../authorization/test_authorization_tags.py | 2 +- unittests/dojo_test_case.py | 15 +- .../vulnerabilityreport_extended.json | 206 +++++++++++++++++ unittests/scans/trufflehog3/issue_6999.json | 21 ++ .../wazuh/one_finding_with_endpoint.json | 29 +++ unittests/scans/wfuzz/issue_7863.json | 14 ++ unittests/scans/yarn_audit/issue_6495.json | 142 ++++++++++++ unittests/test_apiv2_metadata.py | 4 +- unittests/test_apiv2_methods.py | 46 ---- unittests/test_apiv2_methods_and_endpoints.py | 126 +++++++++++ unittests/test_apiv2_scan_import_options.py | 4 +- unittests/test_apiv2_user.py | 2 +- unittests/test_apply_finding_template.py | 6 +- unittests/test_deduplication_logic.py | 2 +- unittests/test_endpoint_meta_import.py | 28 +-- unittests/test_endpoint_model.py | 10 +- unittests/test_factory.py | 8 +- unittests/test_finding_model.py | 148 +++++++++++- unittests/test_import_reimport.py | 16 +- unittests/test_importers_importer.py | 16 +- unittests/test_jira_config_engagement.py | 18 +- unittests/test_jira_config_product.py | 22 +- unittests/test_jira_import_and_pushing_api.py | 36 +-- unittests/test_rest_framework.py | 60 ++--- unittests/test_risk_acceptance.py | 30 +-- unittests/test_tags.py | 44 ++-- unittests/tools/test_acunetix360_parser.py | 2 +- .../tools/test_anchore_enterprise_parser.py | 2 +- unittests/tools/test_anchore_grype_parser.py | 10 +- unittests/tools/test_api_bugcrowd_parser.py | 11 +- unittests/tools/test_auditjs_parser.py | 6 +- unittests/tools/test_burp_api_parser.py | 1 - unittests/tools/test_burp_graphql_parser.py | 2 +- unittests/tools/test_checkmarx_osa_parser.py | 6 +- unittests/tools/test_checkmarx_parser.py | 1 - unittests/tools/test_checkov_parser.py | 2 +- unittests/tools/test_codechecker_parser.py | 16 +- unittests/tools/test_coverity_api_parser.py | 4 +- .../tools/test_dependency_track_parser.py | 6 +- unittests/tools/test_dockerbench_parser.py | 10 +- .../tools/test_gcloud_artifact_scan_parser.py | 2 +- unittests/tools/test_generic_parser.py | 6 +- unittests/tools/test_gitlab_dast_parser.py | 12 +- .../tools/test_gitlab_dep_scan_parser.py | 4 +- unittests/tools/test_gitlab_sast_parser.py | 18 +- unittests/tools/test_govulncheck_parser.py | 8 +- unittests/tools/test_immuniweb_parser.py | 2 +- unittests/tools/test_intsights_parser.py | 2 +- unittests/tools/test_kubebench_parser.py | 2 +- unittests/tools/test_kubehunter_parser.py | 9 +- unittests/tools/test_meterian_parser.py | 20 +- unittests/tools/test_nikto_parser.py | 2 +- unittests/tools/test_npm_audit_parser.py | 11 +- unittests/tools/test_nsp_parser.py | 12 +- .../tools/test_ossindex_devaudit_parser.py | 26 +-- unittests/tools/test_risk_recon_parser.py | 4 +- unittests/tools/test_ssl_labs_parser.py | 10 +- unittests/tools/test_sysdig_reports_parser.py | 14 +- unittests/tools/test_tenable_parser.py | 12 +- unittests/tools/test_threagile_parser.py | 4 +- unittests/tools/test_trivy_operator_parser.py | 15 ++ unittests/tools/test_trufflehog3_parser.py | 6 + unittests/tools/test_veracode_parser.py | 2 - unittests/tools/test_wazuh_parser.py | 51 +++++ unittests/tools/test_wfuzz_parser.py | 10 + unittests/tools/test_yarn_audit_parser.py | 10 +- 124 files changed, 1783 insertions(+), 586 deletions(-) create mode 100644 docs/content/en/getting_started/upgrading/2.32.md create mode 100644 dojo/db_migrations/0200_finding_sla_expiration_date_product_async_updating_and_more.py create mode 100644 dojo/sla_config/helpers.py create mode 100644 unittests/scans/trivy_operator/vulnerabilityreport_extended.json create mode 100644 unittests/scans/trufflehog3/issue_6999.json create mode 100644 unittests/scans/wazuh/one_finding_with_endpoint.json create mode 100644 unittests/scans/wfuzz/issue_7863.json create mode 100644 unittests/scans/yarn_audit/issue_6495.json delete mode 100644 unittests/test_apiv2_methods.py create mode 100644 unittests/test_apiv2_methods_and_endpoints.py create mode 100644 unittests/tools/test_wazuh_parser.py diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index 5b77920339e..05905306de6 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -42,16 +42,17 @@ categories: - title: '🗣 Updates in localization' label: 'localization' - title: '🧰 Maintenance' + collapse-after: 3 labels: - 'dependencies' - 'maintenance' exclude-labels: - - 'skip-changelog' - + - 'skip-changelog' + change-template: '- $TITLE @$AUTHOR (#$NUMBER)' template: | Please consult the [Upgrade notes in the documentation ](https://documentation.defectdojo.com/getting_started/upgrading/) for specific instructions for this release, and general upgrade instructions. Below is an automatically generated list of all PRs merged since the previous release. - + ## Changes since $PREVIOUS_TAG $CHANGES @@ -65,4 +66,4 @@ version-resolver: patch: labels: - 'patch' - default: patch \ No newline at end of file + default: patch diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml index 466dae96905..d05cb191428 100644 --- a/.github/workflows/release-drafter.yml +++ b/.github/workflows/release-drafter.yml @@ -27,7 +27,7 @@ jobs: steps: - name: Create Release id: create_release - uses: release-drafter/release-drafter@v5.25.0 + uses: release-drafter/release-drafter@v6.0.0 with: version: ${{ github.event.inputs.version }} env: diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml index 1e1882d4135..421b3bcd20f 100644 --- a/.github/workflows/ruff.yml +++ b/.github/workflows/ruff.yml @@ -33,4 +33,4 @@ jobs: run: pip install -r requirements-lint.txt - name: Run Ruff Linter - run: ruff dojo \ No newline at end of file + run: ruff . \ No newline at end of file diff --git a/Dockerfile.nginx-alpine b/Dockerfile.nginx-alpine index ae49af4f166..bead9de1f9a 100644 --- a/Dockerfile.nginx-alpine +++ b/Dockerfile.nginx-alpine @@ -140,7 +140,7 @@ COPY manage.py ./ COPY dojo/ ./dojo/ RUN env DD_SECRET_KEY='.' python3 manage.py collectstatic --noinput && true -FROM nginx:1.25.3-alpine@sha256:d12e6f7153fae36843aaeed8144c39956698e084e2e898891fa0cc8fe8f6c95c +FROM nginx:1.25.3-alpine@sha256:f2802c2a9d09c7aa3ace27445dfc5656ff24355da28e7b958074a0111e3fc076 ARG uid=1001 ARG appuser=defectdojo COPY --from=collectstatic /app/static/ /usr/share/nginx/html/static/ diff --git a/Dockerfile.nginx-debian b/Dockerfile.nginx-debian index 4ace9c1d5c8..100f6d546b3 100644 --- a/Dockerfile.nginx-debian +++ b/Dockerfile.nginx-debian @@ -75,7 +75,7 @@ COPY dojo/ ./dojo/ RUN env DD_SECRET_KEY='.' python3 manage.py collectstatic --noinput && true -FROM nginx:1.25.3-alpine@sha256:d12e6f7153fae36843aaeed8144c39956698e084e2e898891fa0cc8fe8f6c95c +FROM nginx:1.25.3-alpine@sha256:f2802c2a9d09c7aa3ace27445dfc5656ff24355da28e7b958074a0111e3fc076 ARG uid=1001 ARG appuser=defectdojo COPY --from=collectstatic /app/static/ /usr/share/nginx/html/static/ diff --git a/components/package.json b/components/package.json index 262ef7e6f3b..9a57f7b78dd 100644 --- a/components/package.json +++ b/components/package.json @@ -1,6 +1,6 @@ { "name": "defectdojo", - "version": "2.31.0-dev", + "version": "2.32.0-dev", "license" : "BSD-3-Clause", "private": true, "dependencies": { diff --git a/docker-compose.yml b/docker-compose.yml index 367424e606b..4394261baf4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -149,7 +149,7 @@ services: volumes: - defectdojo_postgres:/var/lib/postgresql/data rabbitmq: - image: rabbitmq:3.12.12-alpine@sha256:614857f02c0f150a0b1d29b2a03700d34c14dff7d19c85398e968a58ac7517c1 + image: rabbitmq:3.12.12-alpine@sha256:fcd6a66524be55c15c81011dc87cc4b6e4405130fbb950c21ad1d31e8f6322dd profiles: - mysql-rabbitmq - postgres-rabbitmq diff --git a/docker/entrypoint-integration-tests.sh b/docker/entrypoint-integration-tests.sh index 0044d0b5b9e..5a75ed6b5b7 100755 --- a/docker/entrypoint-integration-tests.sh +++ b/docker/entrypoint-integration-tests.sh @@ -29,6 +29,9 @@ export CHROMEDRIVER CHROME_PATH=/opt/chrome/chrome export CHROME_PATH +# We are strict about Warnings during testing +export PYTHONWARNINGS=error + # Run available unittests with a simple setup # All available Integrationtest Scripts are activated below # If successsful, A successs message is printed and the script continues diff --git a/docker/entrypoint-unit-tests-devDocker.sh b/docker/entrypoint-unit-tests-devDocker.sh index e2c63c04fd9..96f9906c177 100755 --- a/docker/entrypoint-unit-tests-devDocker.sh +++ b/docker/entrypoint-unit-tests-devDocker.sh @@ -15,6 +15,9 @@ unset DD_DATABASE_URL # Unset the celery broker URL so that we can force the other DD_CELERY_BROKER settings unset DD_CELERY_BROKER_URL +# We are strict about Warnings during testing +export PYTHONWARNINGS=error + python3 manage.py makemigrations dojo python3 manage.py migrate diff --git a/docker/entrypoint-unit-tests.sh b/docker/entrypoint-unit-tests.sh index 99062aff8a0..16a5e6c29b5 100755 --- a/docker/entrypoint-unit-tests.sh +++ b/docker/entrypoint-unit-tests.sh @@ -16,6 +16,9 @@ unset DD_DATABASE_URL # Unset the celery broker URL so that we can force the other DD_CELERY_BROKER settings unset DD_CELERY_BROKER_URL +# We are strict about Warnings during testing +export PYTHONWARNINGS=error + # TARGET_SETTINGS_FILE=dojo/settings/settings.py # if [ ! -f ${TARGET_SETTINGS_FILE} ]; then # echo "Creating settings.py" diff --git a/docs/content/en/getting_started/upgrading/2.32.md b/docs/content/en/getting_started/upgrading/2.32.md new file mode 100644 index 00000000000..0d04c771e36 --- /dev/null +++ b/docs/content/en/getting_started/upgrading/2.32.md @@ -0,0 +1,7 @@ +--- +title: 'Upgrading to DefectDojo Version 2.32.x' +toc_hide: true +weight: -20240205 +description: No special instructions. +--- +There are no special instructions for upgrading to 2.32.x. Check the [Release Notes](https://github.com/DefectDojo/django-DefectDojo/releases/tag/2.32.0) for the contents of the release. diff --git a/docs/content/en/integrations/parsers/file/hcl_appscan.md b/docs/content/en/integrations/parsers/file/hcl_appscan.md index aae796606f3..2a837039553 100644 --- a/docs/content/en/integrations/parsers/file/hcl_appscan.md +++ b/docs/content/en/integrations/parsers/file/hcl_appscan.md @@ -2,7 +2,7 @@ title: "HCL Appscan" toc_hide: true --- -The HCL Appscan has the possibiilty to export the results in PDF, XML and CSV formats within the portal. However, this parser only supports the import of XML generated from HCL Appscan on cloud. +The HCL Appscan has the possibility to export the results in PDF, XML and CSV formats within the portal. However, this parser only supports the import of XML generated from HCL Appscan on cloud. ### Sample Scan Data Sample HCL Appscan scans can be found [here](https://github.com/DefectDojo/django-DefectDojo/tree/master/unittests/scans/hcl_appscan). \ No newline at end of file diff --git a/docs/content/en/integrations/parsers/file/mend.md b/docs/content/en/integrations/parsers/file/mend.md index bb65935df79..2ec28770586 100644 --- a/docs/content/en/integrations/parsers/file/mend.md +++ b/docs/content/en/integrations/parsers/file/mend.md @@ -12,4 +12,4 @@ Unit tests for Mend JSON files can be found at https://github.com/DefectDojo/dja ### Link To Tool See documentation: https://docs.mend.io/bundle/unified_agent/page/example_of_a_unified_agent_json_report.html -*Formerly known as Whitesource. \ No newline at end of file +*Formerly known as Whitesource.* diff --git a/docs/content/en/integrations/parsers/file/wazuh.md b/docs/content/en/integrations/parsers/file/wazuh.md index 01bb0a0aa79..329372ff84d 100644 --- a/docs/content/en/integrations/parsers/file/wazuh.md +++ b/docs/content/en/integrations/parsers/file/wazuh.md @@ -2,7 +2,51 @@ title: "Wazuh Scanner" toc_hide: true --- -Import JSON report. + +### File Types +DefectDojo parser accepts a .json file from [Wazuh](https://wazuh.com). The export from Wazuh can be done via 2 ways. Choose the one which you prefer. + +- export the Wazuh findings from API and upload them to DefectDojo. This method may be the easiest one but does export all known vulnerabilities at once. It is not possible to sort them after clients or any other categories. You will receive all vulnerabilities in one engagement. It also does not output the endpoint of a finding. +- export the findings via the script [available here](https://github.com/quirinziessler/wazuh-findings-exporter). The script fetches the findings by Wazuh client groups and saves them as json, ready for upload. You will receive one file per group allowing you to separate the clients via engagements in Wazuh. It also exports the endpoints hostname and displays them in DefectDojo UI. + +Independent of your above choice: Have in mind to adjust the max file size via "DD_SCAN_FILE_MAX_SIZE" if you see files larger than the default value of 100MB. Depending on the amount and category of integrated devices, the file size jumps rapidly. + +### Acceptable JSON Format +Parser expects a .json file structured as below. + +~~~ +{ + "data": { + "affected_items": [ + { + "architecture": "amd64", + "condition": "Package less than 4.3.2", + "cve": "CVE-1234-123123", + "cvss2_score": 0, + "cvss3_score": 5.5, + "detection_time": "2023-02-08T13:55:10Z", + "external_references": [ + "https://nvd.nist.gov/vuln/detail/CVE-YYYY-XXXXX", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-YYYY-XXXXX" + ], + "name": "asdf", + "published": "2022-09-01", + "severity": "Medium", + "status": "VALID", + "title": "CVE-YYYY-XXXXX affects asdf", + "type": "PACKAGE", + "updated": "2022-09-07", + "version": "4.3.1" + } + ], + "failed_items": [], + "total_affected_items": 1, + "total_failed_items": 0 + }, + "error": 0, + "message": "All selected vulnerabilities were returned" +} +~~~ ### Sample Scan Data Sample Wazuh Scanner scans can be found [here](https://github.com/DefectDojo/django-DefectDojo/tree/master/unittests/scans/wazuh). \ No newline at end of file diff --git a/docs/content/en/integrations/parsers/file/wfuzz.md b/docs/content/en/integrations/parsers/file/wfuzz.md index 2aa4add793b..1893c359bd2 100644 --- a/docs/content/en/integrations/parsers/file/wfuzz.md +++ b/docs/content/en/integrations/parsers/file/wfuzz.md @@ -9,8 +9,10 @@ The return code matching are directly put in Severity as follow(this is hardcode HTTP Return Code | Severity -----------------|--------- 200 | High +302 | Low 401 | Medium 403 | Medium +404 | Medium 407 | Medium 500 | Low diff --git a/docs/package-lock.json b/docs/package-lock.json index 71809c75120..eaf7096132b 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -6,7 +6,7 @@ "": { "devDependencies": { "autoprefixer": "10.4.17", - "postcss": "8.4.33", + "postcss": "8.4.35", "postcss-cli": "11.0.0" } }, @@ -612,9 +612,9 @@ } }, "node_modules/postcss": { - "version": "8.4.33", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz", - "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==", + "version": "8.4.35", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", + "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", "dev": true, "funding": [ { @@ -1390,9 +1390,9 @@ "dev": true }, "postcss": { - "version": "8.4.33", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz", - "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==", + "version": "8.4.35", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", + "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", "dev": true, "requires": { "nanoid": "^3.3.7", diff --git a/docs/package.json b/docs/package.json index 41633030336..b2185ed2596 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,6 +1,6 @@ { "devDependencies": { - "postcss": "8.4.33", + "postcss": "8.4.35", "autoprefixer": "10.4.17", "postcss-cli": "11.0.0" } diff --git a/dojo/__init__.py b/dojo/__init__.py index 9406e56f475..f1c39c15ed1 100644 --- a/dojo/__init__.py +++ b/dojo/__init__.py @@ -4,6 +4,6 @@ # Django starts so that shared_task will use this app. from .celery import app as celery_app # noqa -__version__ = '2.31.0-dev' +__version__ = '2.32.0-dev' __url__ = 'https://github.com/DefectDojo/django-DefectDojo' __docs__ = 'https://documentation.defectdojo.com' diff --git a/dojo/api_v2/serializers.py b/dojo/api_v2/serializers.py index 49e3486fe2c..ff21d50aff7 100644 --- a/dojo/api_v2/serializers.py +++ b/dojo/api_v2/serializers.py @@ -1527,6 +1527,16 @@ def get_engagement(self, obj): engagement ) + def validate(self, data): + if self.context["request"].method == "POST": + findings = data['accepted_findings'] + for finding in findings: + if not user_has_permission(self.context["request"].user, finding, Permissions.Finding_View): + raise PermissionDenied( + "You are not permitted to add one or more selected findings to this risk acceptance" + ) + return data + class Meta: model = Risk_Acceptance fields = "__all__" @@ -2004,8 +2014,20 @@ class Meta: exclude = ( "tid", "updated", + "async_updating" ) + def validate(self, data): + async_updating = getattr(self.instance, 'async_updating', None) + if async_updating: + new_sla_config = data.get('sla_configuration', None) + old_sla_config = getattr(self.instance, 'sla_configuration', None) + if new_sla_config and old_sla_config and new_sla_config != old_sla_config: + raise serializers.ValidationError( + 'Finding SLA expiration dates are currently being recalculated. The SLA configuration for this product cannot be changed until the calculation is complete.' + ) + return data + def get_findings_count(self, obj) -> int: return obj.findings_count @@ -3031,7 +3053,21 @@ class Meta: class SLAConfigurationSerializer(serializers.ModelSerializer): class Meta: model = SLA_Configuration - fields = "__all__" + exclude = ( + "async_updating", + ) + + def validate(self, data): + async_updating = getattr(self.instance, 'async_updating', None) + if async_updating: + for field in ['critical', 'high', 'medium', 'low']: + old_days = getattr(self.instance, field, None) + new_days = data.get(field, None) + if old_days and new_days and (old_days != new_days): + raise serializers.ValidationError( + 'Finding SLA expiration dates are currently being calculated. The SLA days for this SLA configuration cannot be changed until the calculation is complete.' + ) + return data class UserProfileSerializer(serializers.Serializer): diff --git a/dojo/api_v2/views.py b/dojo/api_v2/views.py index 40bc45b892b..fceb87c7ea2 100644 --- a/dojo/api_v2/views.py +++ b/dojo/api_v2/views.py @@ -743,12 +743,7 @@ def download_file(self, request, file_id, pk=None): class RiskAcceptanceViewSet( - prefetch.PrefetchListMixin, - prefetch.PrefetchRetrieveMixin, - mixins.DestroyModelMixin, - mixins.UpdateModelMixin, - viewsets.ReadOnlyModelViewSet, - dojo_mixins.DeletePreviewModelMixin, + PrefetchDojoModelViewSet ): serializer_class = serializers.RiskAcceptanceSerializer queryset = Risk_Acceptance.objects.none() diff --git a/dojo/apps.py b/dojo/apps.py index 30a1711b19e..6c84a420de8 100644 --- a/dojo/apps.py +++ b/dojo/apps.py @@ -74,6 +74,7 @@ def ready(self): import dojo.announcement.signals # noqa import dojo.product.signals # noqa import dojo.test.signals # noqa + import dojo.sla_config.helpers # noqa def get_model_fields_with_extra(model, extra_fields=()): diff --git a/dojo/db_migrations/0197_parser_merge.py b/dojo/db_migrations/0197_parser_merge.py index b00dac78a46..613ebea02fa 100644 --- a/dojo/db_migrations/0197_parser_merge.py +++ b/dojo/db_migrations/0197_parser_merge.py @@ -76,7 +76,7 @@ def migrate_clairklar_parsers(apps, schema_editor): clair_test_type, _ = test_type_model.objects.get_or_create(name="Clair Scan", active=True) clairklar_test_type = test_type_model.objects.filter(name="Clair Klar Scan").first() # Get all the findings found by Clair Klar Scan - findings = finding_model.objects.filter(test__scan_type__in=OPENVAS_REFERENCES) + findings = finding_model.objects.filter(test__scan_type__in=CLAIRKLAR_REFERENCES) logger.warning(f'We identified {findings.count()} Clair Klar Scan findings to migrate to Clair Scan findings') # Iterate over all findings and change for finding in findings: diff --git a/dojo/db_migrations/0200_finding_sla_expiration_date_product_async_updating_and_more.py b/dojo/db_migrations/0200_finding_sla_expiration_date_product_async_updating_and_more.py new file mode 100644 index 00000000000..20ef3e4f689 --- /dev/null +++ b/dojo/db_migrations/0200_finding_sla_expiration_date_product_async_updating_and_more.py @@ -0,0 +1,31 @@ +# Generated by Django 4.1.13 on 2024-01-17 03:07 + +from django.db import migrations, models +import logging + +logger = logging.getLogger(__name__) + + +class Migration(migrations.Migration): + + dependencies = [ + ('dojo', '0199_whitesource_to_mend'), + ] + + operations = [ + migrations.AddField( + model_name='finding', + name='sla_expiration_date', + field=models.DateField(blank=True, help_text="(readonly)The date SLA expires for this finding. Empty by default, causing a fallback to 'date'.", null=True, verbose_name='SLA Expiration Date'), + ), + migrations.AddField( + model_name='product', + name='async_updating', + field=models.BooleanField(default=False, help_text='Findings under this Product or SLA configuration are asynchronously being updated'), + ), + migrations.AddField( + model_name='sla_configuration', + name='async_updating', + field=models.BooleanField(default=False, help_text='Findings under this SLA configuration are asynchronously being updated'), + ), + ] diff --git a/dojo/filters.py b/dojo/filters.py index e1cc349d64b..267d1d4b23d 100644 --- a/dojo/filters.py +++ b/dojo/filters.py @@ -147,13 +147,13 @@ class FindingSLAFilter(ChoiceFilter): def any(self, qs, name): return qs - def satisfies_sla(self, qs, name): + def sla_satisfied(self, qs, name): for finding in qs: if finding.violates_sla: qs = qs.exclude(id=finding.id) return qs - def violates_sla(self, qs, name): + def sla_violated(self, qs, name): for finding in qs: if not finding.violates_sla: qs = qs.exclude(id=finding.id) @@ -161,8 +161,8 @@ def violates_sla(self, qs, name): options = { None: (_('Any'), any), - 0: (_('False'), satisfies_sla), - 1: (_('True'), violates_sla), + 0: (_('False'), sla_satisfied), + 1: (_('True'), sla_violated), } def __init__(self, *args, **kwargs): @@ -182,13 +182,13 @@ class ProductSLAFilter(ChoiceFilter): def any(self, qs, name): return qs - def satisfies_sla(self, qs, name): + def sla_satisifed(self, qs, name): for product in qs: if product.violates_sla: qs = qs.exclude(id=product.id) return qs - def violates_sla(self, qs, name): + def sla_violated(self, qs, name): for product in qs: if not product.violates_sla: qs = qs.exclude(id=product.id) @@ -196,8 +196,8 @@ def violates_sla(self, qs, name): options = { None: (_('Any'), any), - 0: (_('False'), satisfies_sla), - 1: (_('True'), violates_sla), + 0: (_('False'), sla_satisifed), + 1: (_('True'), sla_violated), } def __init__(self, *args, **kwargs): @@ -1471,9 +1471,8 @@ class Meta: 'endpoints', 'references', 'thread_id', 'notes', 'scanner_confidence', 'numerical_severity', 'line', 'duplicate_finding', - 'hash_code', - 'reviewers', - 'created', 'files', 'sla_start_date', 'cvssv3', + 'hash_code', 'reviewers', 'created', 'files', + 'sla_start_date', 'sla_expiration_date', 'cvssv3', 'severity_justification', 'steps_to_reproduce', 'epss_score', 'epss_percentile'] diff --git a/dojo/forms.py b/dojo/forms.py index b544c09d05f..558c09ae69d 100755 --- a/dojo/forms.py +++ b/dojo/forms.py @@ -263,6 +263,12 @@ def __init__(self, *args, **kwargs): super(ProductForm, self).__init__(*args, **kwargs) self.fields['prod_type'].queryset = get_authorized_product_types(Permissions.Product_Type_Add_Product) + # if this product has findings being asynchronously updated, disable the sla config field + if self.instance.async_updating: + self.fields['sla_configuration'].disabled = True + self.fields['sla_configuration'].widget.attrs['message'] = 'Finding SLA expiration dates are currently being recalculated. ' + \ + 'This field cannot be changed until the calculation is complete.' + class Meta: model = Product fields = ['name', 'description', 'tags', 'product_manager', 'technical_contact', 'team_manager', 'prod_type', 'sla_configuration', 'regulations', @@ -1073,7 +1079,7 @@ class AdHocFindingForm(forms.ModelForm): # the only reliable way without hacking internal fields to get predicatble ordering is to make it explicit field_order = ('title', 'date', 'cwe', 'vulnerability_ids', 'severity', 'cvssv3', 'description', 'mitigation', 'impact', 'request', 'response', 'steps_to_reproduce', 'severity_justification', 'endpoints', 'endpoints_to_add', 'references', 'active', 'verified', 'false_p', 'duplicate', 'out_of_scope', - 'risk_accepted', 'under_defect_review', 'sla_start_date') + 'risk_accepted', 'under_defect_review', 'sla_start_date', 'sla_expiration_date') def __init__(self, *args, **kwargs): req_resp = kwargs.pop('req_resp') @@ -1113,7 +1119,8 @@ def clean(self): class Meta: model = Finding exclude = ('reporter', 'url', 'numerical_severity', 'under_review', 'reviewers', 'cve', 'inherited_tags', - 'review_requested_by', 'is_mitigated', 'jira_creation', 'jira_change', 'endpoint_status', 'sla_start_date') + 'review_requested_by', 'is_mitigated', 'jira_creation', 'jira_change', 'endpoints', 'sla_start_date', + 'sla_expiration_date') class PromoteFindingForm(forms.ModelForm): @@ -1139,9 +1146,9 @@ class PromoteFindingForm(forms.ModelForm): references = forms.CharField(widget=forms.Textarea, required=False) # the onyl reliable way without hacking internal fields to get predicatble ordering is to make it explicit - field_order = ('title', 'group', 'date', 'sla_start_date', 'cwe', 'vulnerability_ids', 'severity', 'cvssv3', 'cvssv3_score', 'description', 'mitigation', 'impact', - 'request', 'response', 'steps_to_reproduce', 'severity_justification', 'endpoints', 'endpoints_to_add', 'references', - 'active', 'mitigated', 'mitigated_by', 'verified', 'false_p', 'duplicate', + field_order = ('title', 'group', 'date', 'sla_start_date', 'sla_expiration_date', 'cwe', 'vulnerability_ids', 'severity', 'cvssv3', + 'cvssv3_score', 'description', 'mitigation', 'impact', 'request', 'response', 'steps_to_reproduce', 'severity_justification', + 'endpoints', 'endpoints_to_add', 'references', 'active', 'mitigated', 'mitigated_by', 'verified', 'false_p', 'duplicate', 'out_of_scope', 'risk_accept', 'under_defect_review') def __init__(self, *args, **kwargs): @@ -1211,9 +1218,9 @@ class FindingForm(forms.ModelForm): 'invalid_choice': EFFORT_FOR_FIXING_INVALID_CHOICE}) # the only reliable way without hacking internal fields to get predicatble ordering is to make it explicit - field_order = ('title', 'group', 'date', 'sla_start_date', 'cwe', 'vulnerability_ids', 'severity', 'cvssv3', 'cvssv3_score', 'description', 'mitigation', 'impact', - 'request', 'response', 'steps_to_reproduce', 'severity_justification', 'endpoints', 'endpoints_to_add', 'references', - 'active', 'mitigated', 'mitigated_by', 'verified', 'false_p', 'duplicate', + field_order = ('title', 'group', 'date', 'sla_start_date', 'sla_expiration_date', 'cwe', 'vulnerability_ids', 'severity', 'cvssv3', + 'cvssv3_score', 'description', 'mitigation', 'impact', 'request', 'response', 'steps_to_reproduce', 'severity_justification', + 'endpoints', 'endpoints_to_add', 'references', 'active', 'mitigated', 'mitigated_by', 'verified', 'false_p', 'duplicate', 'out_of_scope', 'risk_accept', 'under_defect_review') def __init__(self, *args, **kwargs): @@ -1251,6 +1258,7 @@ def __init__(self, *args, **kwargs): self.fields['duplicate'].help_text = "You can mark findings as duplicate only from the view finding page." self.fields['sla_start_date'].disabled = True + self.fields['sla_expiration_date'].disabled = True if self.can_edit_mitigated_data: if hasattr(self, 'instance'): @@ -2436,6 +2444,22 @@ def clean(self): class SLAConfigForm(forms.ModelForm): + def __init__(self, *args, **kwargs): + super(SLAConfigForm, self).__init__(*args, **kwargs) + + # if this sla config has findings being asynchronously updated, disable the days by severity fields + if self.instance.async_updating: + msg = 'Finding SLA expiration dates are currently being recalculated. ' + \ + 'This field cannot be changed until the calculation is complete.' + self.fields['critical'].disabled = True + self.fields['critical'].widget.attrs['message'] = msg + self.fields['high'].disabled = True + self.fields['high'].widget.attrs['message'] = msg + self.fields['medium'].disabled = True + self.fields['medium'].widget.attrs['message'] = msg + self.fields['low'].disabled = True + self.fields['low'].widget.attrs['message'] = msg + class Meta: model = SLA_Configuration fields = ['name', 'description', 'critical', 'high', 'medium', 'low'] diff --git a/dojo/importers/importer/importer.py b/dojo/importers/importer/importer.py index cb7af1e728a..4b3b1d43c6c 100644 --- a/dojo/importers/importer/importer.py +++ b/dojo/importers/importer/importer.py @@ -76,7 +76,24 @@ def process_parsed_findings(self, test, parsed_findings, scan_type, user, active for item in items: # FIXME hack to remove when all parsers have unit tests for this attribute - if item.severity.lower().startswith('info') and item.severity != 'Info': + # Importing the cvss module via: + # `from cvss import CVSS3` + # _and_ given a CVSS vector string such as: + # cvss_vector_str = 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:N', + # the following severity calculation returns the + # string values of, "None" instead of the expected string values + # of "Info": + # ``` + # cvss_obj = CVSS3(cvss_vector_str) + # severities = cvss_obj.severities() + # print(severities) + # ('None', 'None', 'None') + # print(severities[0]) + # 'None' + # print(type(severities[0])) + # + # ``` + if (item.severity.lower().startswith('info') or item.severity.lower() == 'none') and item.severity != 'Info': item.severity = 'Info' item.numerical_severity = Finding.get_numerical_severity(item.severity) diff --git a/dojo/models.py b/dojo/models.py index 8b4d82210ec..2b6beb27b49 100755 --- a/dojo/models.py +++ b/dojo/models.py @@ -31,6 +31,7 @@ from django import forms from django.utils.translation import gettext as _ from dateutil.relativedelta import relativedelta +from datetime import datetime from tagulous.models import TagField from tagulous.models.managers import FakeTagRelatedManager import tagulous.admin @@ -856,9 +857,7 @@ class Meta: class SLA_Configuration(models.Model): name = models.CharField(max_length=128, unique=True, blank=False, verbose_name=_('Custom SLA Name'), - help_text=_('A unique name for the set of SLAs.') - ) - + help_text=_('A unique name for the set of SLAs.')) description = models.CharField(max_length=512, null=True, blank=True) critical = models.IntegerField(default=7, verbose_name=_('Critical Finding SLA Days'), help_text=_('number of days to remediate a critical finding.')) @@ -868,15 +867,56 @@ class SLA_Configuration(models.Model): help_text=_('number of days to remediate a medium finding.')) low = models.IntegerField(default=120, verbose_name=_('Low Finding SLA Days'), help_text=_('number of days to remediate a low finding.')) + async_updating = models.BooleanField(default=False, + help_text=_('Findings under this SLA configuration are asynchronously being updated')) def clean(self): - sla_days = [self.critical, self.high, self.medium, self.low] for sla_day in sla_days: if sla_day < 1: raise ValidationError('SLA Days must be at least 1') + def save(self, *args, **kwargs): + # get the initial sla config before saving (if this is an existing sla config) + initial_sla_config = None + if self.pk is not None: + initial_sla_config = SLA_Configuration.objects.get(pk=self.pk) + # if initial config exists and async finding update is already running, revert sla config before saving + if initial_sla_config and self.async_updating: + self.critical = initial_sla_config.critical + self.high = initial_sla_config.high + self.medium = initial_sla_config.medium + self.low = initial_sla_config.low + + super(SLA_Configuration, self).save(*args, **kwargs) + + # if the initial sla config exists and async finding update is not running + if initial_sla_config is not None and not self.async_updating: + # check which sla days fields changed based on severity + severities = [] + if initial_sla_config.critical != self.critical: + severities.append('Critical') + if initial_sla_config.high != self.high: + severities.append('High') + if initial_sla_config.medium != self.medium: + severities.append('Medium') + if initial_sla_config.low != self.low: + severities.append('Low') + # if severities have changed, update finding sla expiration dates with those severities + if len(severities): + # set the async updating flag to true for this sla config + self.async_updating = True + super(SLA_Configuration, self).save(*args, **kwargs) + # set the async updating flag to true for all products using this sla config + products = Product.objects.filter(sla_configuration=self) + for product in products: + product.async_updating = True + super(Product, product).save() + # launch the async task to update all finding sla expiration dates + from dojo.sla_config.helpers import update_sla_expiration_dates_sla_config_async + update_sla_expiration_dates_sla_config_async(self, tuple(severities), products) + def __str__(self): return self.name @@ -998,6 +1038,37 @@ class Product(models.Model): blank=False, verbose_name=_("Disable SLA breach notifications"), help_text=_("Disable SLA breach notifications if configured in the global settings")) + async_updating = models.BooleanField(default=False, + help_text=_('Findings under this Product or SLA configuration are asynchronously being updated')) + + def save(self, *args, **kwargs): + # get the product's sla config before saving (if this is an existing product) + initial_sla_config = None + if self.pk is not None: + initial_sla_config = getattr(Product.objects.get(pk=self.pk), 'sla_configuration', None) + # if initial sla config exists and async finding update is already running, revert sla config before saving + if initial_sla_config and self.async_updating: + self.sla_configuration = initial_sla_config + + super(Product, self).save(*args, **kwargs) + + # if the initial sla config exists and async finding update is not running + if initial_sla_config is not None and not self.async_updating: + # get the new sla config from the saved product + new_sla_config = getattr(self, 'sla_configuration', None) + # if the sla config has changed, update finding sla expiration dates within this product + if new_sla_config and (initial_sla_config != new_sla_config): + # set the async updating flag to true for this product + self.async_updating = True + super(Product, self).save(*args, **kwargs) + # set the async updating flag to true for the sla config assigned to this product + sla_config = getattr(self, 'sla_configuration', None) + if sla_config: + sla_config.async_updating = True + super(SLA_Configuration, sla_config).save() + # launch the async task to update all finding sla expiration dates + from dojo.product.helpers import update_sla_expiration_dates_product_async + update_sla_expiration_dates_product_async(self, sla_config) def __str__(self): return self.name @@ -1123,8 +1194,7 @@ def get_absolute_url(self): @property def violates_sla(self): - findings = Finding.objects.filter(test__engagement__product=self, - active=True) + findings = Finding.objects.filter(test__engagement__product=self, active=True) for f in findings: if f.violates_sla: return True @@ -2110,20 +2180,22 @@ def __str__(self): class Finding(models.Model): - title = models.CharField(max_length=511, verbose_name=_('Title'), help_text=_("A short description of the flaw.")) date = models.DateField(default=get_current_date, verbose_name=_('Date'), help_text=_("The date the flaw was discovered.")) - sla_start_date = models.DateField( blank=True, null=True, verbose_name=_('SLA Start Date'), help_text=_("(readonly)The date used as start date for SLA calculation. Set by expiring risk acceptances. Empty by default, causing a fallback to 'date'.")) - + sla_expiration_date = models.DateField( + blank=True, + null=True, + verbose_name=_('SLA Expiration Date'), + help_text=_("(readonly)The date SLA expires for this finding. Empty by default, causing a fallback to 'date'.")) cwe = models.IntegerField(default=0, null=True, blank=True, verbose_name=_("CWE"), help_text=_("The CWE number associated with this flaw.")) @@ -2760,19 +2832,28 @@ def status(self): return ", ".join([str(s) for s in status]) def _age(self, start_date): + from dateutil.parser import parse + if start_date and isinstance(start_date, str): + start_date = parse(start_date).date() + from dojo.utils import get_work_days if settings.SLA_BUSINESS_DAYS: if self.mitigated: - days = get_work_days(self.date, self.mitigated.date()) + mitigated_date = self.mitigated + if isinstance(mitigated_date, datetime): + mitigated_date = self.mitigated.date() + days = get_work_days(self.date, mitigated_date) else: days = get_work_days(self.date, get_current_date()) else: - from datetime import datetime if isinstance(start_date, datetime): start_date = start_date.date() if self.mitigated: - diff = self.mitigated.date() - start_date + mitigated_date = self.mitigated + if isinstance(mitigated_date, datetime): + mitigated_date = self.mitigated.date() + diff = mitigated_date - start_date else: diff = get_current_date() - start_date days = diff.days @@ -2782,9 +2863,9 @@ def _age(self, start_date): def age(self): return self._age(self.date) - def get_sla_periods(self): - sla_configuration = SLA_Configuration.objects.filter(id=self.test.engagement.product.sla_configuration_id).first() - return sla_configuration + @property + def sla_age(self): + return self._age(self.get_sla_start_date()) def get_sla_start_date(self): if self.sla_start_date: @@ -2792,16 +2873,34 @@ def get_sla_start_date(self): else: return self.date - @property - def sla_age(self): - return self._age(self.get_sla_start_date()) + def get_sla_period(self): + sla_configuration = SLA_Configuration.objects.filter(id=self.test.engagement.product.sla_configuration_id).first() + return getattr(sla_configuration, self.severity.lower(), None) + + def set_sla_expiration_date(self): + system_settings = System_Settings.objects.get() + if not system_settings.enable_finding_sla: + return None + + days_remaining = None + sla_period = self.get_sla_period() + if sla_period: + days_remaining = sla_period - self.sla_age + + if days_remaining: + if self.mitigated: + mitigated_date = self.mitigated + if isinstance(mitigated_date, datetime): + mitigated_date = self.mitigated.date() + self.sla_expiration_date = mitigated_date + relativedelta(days=days_remaining) + else: + self.sla_expiration_date = get_current_date() + relativedelta(days=days_remaining) def sla_days_remaining(self): sla_calculation = None - sla_periods = self.get_sla_periods() - sla_age = getattr(sla_periods, self.severity.lower(), None) - if sla_age: - sla_calculation = sla_age - self.sla_age + sla_period = self.get_sla_period() + if sla_period: + sla_calculation = sla_period - self.sla_age return sla_calculation def sla_deadline(self): @@ -2933,6 +3032,9 @@ def save(self, dedupe_option=True, rules_option=True, product_grading_option=Tru elif (self.file_path is not None): self.static_finding = True + # update the SLA expiration date last, after all other finding fields have been updated + self.set_sla_expiration_date() + logger.debug("Saving finding of id " + str(self.id) + " dedupe_option:" + str(dedupe_option) + " (self.pk is %s)", "None" if self.pk is None else "not None") super(Finding, self).save(*args, **kwargs) diff --git a/dojo/product/helpers.py b/dojo/product/helpers.py index c2d3f634aec..74530744cde 100644 --- a/dojo/product/helpers.py +++ b/dojo/product/helpers.py @@ -1,11 +1,31 @@ import contextlib -from celery.utils.log import get_task_logger +import logging from dojo.celery import app -from dojo.models import Product, Engagement, Test, Finding, Endpoint +from dojo.models import SLA_Configuration, Product, Engagement, Test, Finding, Endpoint from dojo.decorators import dojo_async_task -logger = get_task_logger(__name__) +logger = logging.getLogger(__name__) + + +@dojo_async_task +@app.task +def update_sla_expiration_dates_product_async(product, sla_config, *args, **kwargs): + update_sla_expiration_dates_product_sync(product, sla_config) + + +def update_sla_expiration_dates_product_sync(product, sla_config): + logger.info(f"Updating finding SLA expiration dates within product {product}") + # update each finding that is within the SLA configuration that was saved + for f in Finding.objects.filter(test__engagement__product=product): + f.save() + # reset the async updating flag to false for the sla config assigned to this product + if sla_config: + sla_config.async_updating = False + super(SLA_Configuration, sla_config).save() + # set the async updating flag to false for the sla config assigned to this product + product.async_updating = False + super(Product, product).save() @dojo_async_task diff --git a/dojo/product/views.py b/dojo/product/views.py index be1b9afe0c8..c2dc16098cd 100755 --- a/dojo/product/views.py +++ b/dojo/product/views.py @@ -873,10 +873,15 @@ def edit_product(request, pid): form = ProductForm(request.POST, instance=product) jira_project = jira_helper.get_jira_project(product) if form.is_valid(): + initial_sla_config = Product.objects.get(pk=form.instance.id).sla_configuration form.save() + msg = 'Product updated successfully.' + # check if the SLA config was changed, append additional context to message + if initial_sla_config != form.instance.sla_configuration: + msg += ' All SLA expiration dates for findings within this product will be recalculated asynchronously for the newly assigned SLA configuration.' messages.add_message(request, messages.SUCCESS, - _('Product updated successfully.'), + _(msg), extra_tags='alert-success') success, jform = jira_helper.process_jira_project_form(request, instance=jira_project, product=product) diff --git a/dojo/settings/settings.dist.py b/dojo/settings/settings.dist.py index d8eec52a74b..fad2454b7ca 100644 --- a/dojo/settings/settings.dist.py +++ b/dojo/settings/settings.dist.py @@ -7,6 +7,7 @@ from netaddr import IPNetwork, IPSet import json import logging +import warnings logger = logging.getLogger(__name__) @@ -40,6 +41,7 @@ DD_SECURE_CONTENT_TYPE_NOSNIFF=(bool, True), DD_CSRF_COOKIE_SAMESITE=(str, 'Lax'), DD_SESSION_COOKIE_SAMESITE=(str, 'Lax'), + DD_APPEND_SLASH=(bool, True), DD_TIME_ZONE=(str, 'UTC'), DD_LANG=(str, 'en-us'), DD_TEAM_NAME=(str, 'Security Team'), @@ -672,6 +674,9 @@ def generate_url(scheme, double_slashes, user, password, host, port, path, param SESSION_COOKIE_SECURE = env('DD_SESSION_COOKIE_SECURE') SESSION_COOKIE_SAMESITE = env('DD_SESSION_COOKIE_SAMESITE') +# Override default Django behavior for incorrect URLs +APPEND_SLASH = env('DD_APPEND_SLASH') + # Whether to use a secure cookie for the CSRF cookie. CSRF_COOKIE_SECURE = env('DD_CSRF_COOKIE_SECURE') CSRF_COOKIE_SAMESITE = env('DD_CSRF_COOKIE_SAMESITE') @@ -1209,7 +1214,7 @@ def saml2_attrib_map_format(dict): 'SonarQube API Import': ['title', 'file_path', 'line'], 'Dependency Check Scan': ['title', 'cwe', 'file_path'], 'Dockle Scan': ['title', 'description', 'vuln_id_from_tool'], - 'Dependency Track Finding Packaging Format (FPF) Export': ['component_name', 'component_version', 'vulnerability_ids', 'severity'], + 'Dependency Track Finding Packaging Format (FPF) Export': ['component_name', 'component_version', 'vulnerability_ids'], 'Mobsfscan Scan': ['title', 'severity', 'cwe'], 'Tenable Scan': ['title', 'severity', 'vulnerability_ids', 'cwe'], 'Nexpose Scan': ['title', 'severity', 'vulnerability_ids', 'cwe'], @@ -1262,7 +1267,6 @@ def saml2_attrib_map_format(dict): 'NeuVector (compliance)': ['title', 'vuln_id_from_tool', 'description'], 'Wpscan': ['title', 'description', 'severity'], 'Popeye Scan': ['title', 'description'], - 'Wazuh Scan': ['title'], 'Nuclei Scan': ['title', 'cwe', 'severity'], 'KubeHunter Scan': ['title', 'description'], 'kube-bench Scan': ['title', 'vuln_id_from_tool', 'description'], @@ -1476,6 +1480,7 @@ def saml2_attrib_map_format(dict): 'kube-bench Scan': DEDUPE_ALGO_HASH_CODE, 'Threagile risks report': DEDUPE_ALGO_UNIQUE_ID_FROM_TOOL_OR_HASH_CODE, 'Humble Json Importer': DEDUPE_ALGO_HASH_CODE, + 'Wazuh Scan': DEDUPE_ALGO_UNIQUE_ID_FROM_TOOL, 'MSDefender Parser': DEDUPE_ALGO_HASH_CODE, 'HCLAppScan XML': DEDUPE_ALGO_HASH_CODE, } @@ -1714,3 +1719,12 @@ def saml2_attrib_map_format(dict): AUDITLOG_FLUSH_RETENTION_PERIOD = env('DD_AUDITLOG_FLUSH_RETENTION_PERIOD') ENABLE_AUDITLOG = env('DD_ENABLE_AUDITLOG') USE_FIRST_SEEN = env('DD_USE_FIRST_SEEN') + +# TODO - these warnings needs to be removed +if DEBUG: + from django.utils.deprecation import RemovedInDjango50Warning + warnings.filterwarnings("ignore", category=RemovedInDjango50Warning) + warnings.filterwarnings("ignore", message="invalid escape sequence.*") + warnings.filterwarnings("ignore", message="'cgi' is deprecated and slated for removal in Python 3\\.13") + warnings.filterwarnings("ignore", message="DateTimeField .+ received a naive datetime .+ while time zone support is active\\.") + warnings.filterwarnings("ignore", message="unclosed file .+") diff --git a/dojo/sla_config/helpers.py b/dojo/sla_config/helpers.py new file mode 100644 index 00000000000..e9665adce45 --- /dev/null +++ b/dojo/sla_config/helpers.py @@ -0,0 +1,26 @@ +import logging +from dojo.models import SLA_Configuration, Product, Finding +from dojo.celery import app +from dojo.decorators import dojo_async_task + +logger = logging.getLogger(__name__) + + +@dojo_async_task +@app.task +def update_sla_expiration_dates_sla_config_async(sla_config, severities, products, *args, **kwargs): + update_sla_expiration_dates_sla_config_sync(sla_config, severities, products) + + +def update_sla_expiration_dates_sla_config_sync(sla_config, severities, products): + logger.info(f"Updating finding SLA expiration dates within the {sla_config} SLA configuration") + # update each finding that is within the SLA configuration that was saved + for f in Finding.objects.filter(test__engagement__product__sla_configuration_id=sla_config.id, severity__in=severities): + f.save() + # reset the async updating flag to false for all products using this sla config + for product in products: + product.async_updating = False + super(Product, product).save() + # reset the async updating flag to false for this sla config + sla_config.async_updating = False + super(SLA_Configuration, sla_config).save() diff --git a/dojo/sla_config/views.py b/dojo/sla_config/views.py index f247cd77253..e85b06ea8fc 100644 --- a/dojo/sla_config/views.py +++ b/dojo/sla_config/views.py @@ -8,7 +8,7 @@ from dojo.authorization.authorization import user_has_configuration_permission_or_403 from dojo.authorization.authorization_decorators import user_is_configuration_authorized from dojo.forms import SLAConfigForm -from dojo.models import SLA_Configuration, System_Settings +from dojo.models import SLA_Configuration, System_Settings, Product from dojo.utils import add_breadcrumb logger = logging.getLogger(__name__) @@ -41,13 +41,20 @@ def edit_sla_config(request, slaid): if request.method == 'POST' and request.POST.get('delete'): if sla_config.id != 1: - user_has_configuration_permission_or_403( - request.user, 'dojo.delete_sla_configuration') - sla_config.delete() - messages.add_message(request, - messages.SUCCESS, - 'SLA Configuration Deleted.', - extra_tags='alert-success') + if Product.objects.filter(sla_configuration=sla_config).count(): + msg = f"The \"{sla_config}\" SLA configuration could not be deleted, as it is currently in use by one or more products." + messages.add_message(request, + messages.ERROR, + msg, + extra_tags='alert-warning') + else: + user_has_configuration_permission_or_403( + request.user, 'dojo.delete_sla_configuration') + sla_config.delete() + messages.add_message(request, + messages.SUCCESS, + 'SLA Configuration Deleted.', + extra_tags='alert-success') return HttpResponseRedirect(reverse('sla_config', )) else: messages.add_message(request, @@ -59,12 +66,12 @@ def edit_sla_config(request, slaid): elif request.method == 'POST': form = SLAConfigForm(request.POST, instance=sla_config) if form.is_valid(): - form.save() + form.save(commit=True) messages.add_message(request, messages.SUCCESS, - 'SLA configuration successfully updated.', + 'SLA configuration successfully updated. All SLA expiration dates for findings within this SLA configuration will be recalculated asynchronously.', extra_tags='alert-success') - form.save(commit=True) + return HttpResponseRedirect(reverse('sla_config', )) else: form = SLAConfigForm(instance=sla_config) diff --git a/dojo/templates/base.html b/dojo/templates/base.html index 7157a738964..8e42e4278a6 100644 --- a/dojo/templates/base.html +++ b/dojo/templates/base.html @@ -170,12 +170,6 @@ {% endif %} {% endif %} -
  • - - - {% trans "API v2 OpenAPI2 Docs (Deprecated)" %} - -
  • diff --git a/dojo/templates/dojo/form_fields.html b/dojo/templates/dojo/form_fields.html index fe2b949162c..98706ee46d3 100644 --- a/dojo/templates/dojo/form_fields.html +++ b/dojo/templates/dojo/form_fields.html @@ -73,8 +73,7 @@ {% endif %}
    {{ field|addcss:"class:form-control" }} - - +

    {{ field.field.widget.attrs.message }}

    {% for error in field.errors %} {{ error }} {% endfor %} diff --git a/dojo/templates/issue-trackers/jira_full/jira-description.tpl b/dojo/templates/issue-trackers/jira_full/jira-description.tpl index b4e60a64a88..6fd326efb5f 100644 --- a/dojo/templates/issue-trackers/jira_full/jira-description.tpl +++ b/dojo/templates/issue-trackers/jira_full/jira-description.tpl @@ -25,7 +25,7 @@ {% endif %} {% if finding.cvssv3_score %} -*CVSSv3 Score:* {{ finding.cvssv3_score }} +*CVSSv3 Score:* {{ finding.cvssv3_score }} {% if finding.cvssv3 %}({{ finding.cvssv3 }}){% endif %} {% endif %} *Product/Engagement/Test:* [{{ finding.test.engagement.product.name }}|{{ product_url|full_url }}] / [{{ finding.test.engagement.name }}|{{ engagement_url|full_url }}] / [{{ finding.test }}|{{ test_url|full_url }}] diff --git a/dojo/templatetags/display_tags.py b/dojo/templatetags/display_tags.py index d0251eaad43..0095428194e 100644 --- a/dojo/templatetags/display_tags.py +++ b/dojo/templatetags/display_tags.py @@ -255,7 +255,7 @@ def finding_sla(finding): title = "" severity = finding.severity find_sla = finding.sla_days_remaining() - sla_age = getattr(finding.get_sla_periods(), severity.lower(), None) + sla_age = finding.get_sla_period() if finding.mitigated: status = "blue" status_text = 'Remediated within SLA for ' + severity.lower() + ' findings (' + str(sla_age) + ' days since ' + finding.get_sla_start_date().strftime("%b %d, %Y") + ')' diff --git a/dojo/tools/api_bugcrowd/parser.py b/dojo/tools/api_bugcrowd/parser.py index d78b2eb5af4..fee43f09d87 100644 --- a/dojo/tools/api_bugcrowd/parser.py +++ b/dojo/tools/api_bugcrowd/parser.py @@ -138,13 +138,19 @@ def get_findings(self, file, test): verified=self.is_verified(bugcrowd_state), false_p=self.is_false_p(bugcrowd_state), out_of_scope=self.is_out_of_scope(bugcrowd_state), - risk_accepted=self.is_risk_accepted(bugcrowd_state), is_mitigated=self.is_mitigated(bugcrowd_state), static_finding=False, dynamic_finding=True, unique_id_from_tool=unique_id_from_tool, references=links, ) + + if self.is_not_applicable(bugcrowd_state): + # From Bugcrowd - Not Applicable: A submission that you reject because it does not apply to your application. + # Because of this, setting finding to inactive and to Informational + finding.active = False + finding.severity = "Info" + if bug_endpoint: try: bug_endpoint.clean() @@ -227,16 +233,34 @@ def is_active(self, bugcrowd_state): self.is_mitigated(bugcrowd_state) or self.is_false_p(bugcrowd_state) or self.is_out_of_scope(bugcrowd_state) - or self.is_risk_accepted(bugcrowd_state) or bugcrowd_state == "not_reproducible" or bugcrowd_state == "informational" ) + # From https://docs.bugcrowd.com/customers/submission-management/submission-status/ + # Status Options + # There are three categories of statuses: open, accepted, and rejected. Within each category are the following statuses: + + # Open + # New: A submission that has not been reviewed or assigned a status. + # Triaged: A submission that has been confirmed valid and unique by the Bugcrowd ASE team and is ready for the customer to accept. + + # Accepted + # Unresolved: A valid submission that needs to be fixed. Typically, you should reward a submission at this point in the process. + # Resolved: A valid submission that has been fixed. + # Informational: A submission that is reproducible but will not be fixed. Use this if the submission is a best practice issue but + # will not be fixed, a minor priority issue, or if you already have a mitigation. + + # Rejected + # Out of Scope: A submission you reject because it is not in scope with the criteria outlined in the bounty program. + # Not Reproducible: A submission you reject because you cannot reproduce it based on the information you have. + # Not Applicable: A submission that you reject because it does not apply to your application. + def is_duplicate(self, bugcrowd_state): return bugcrowd_state == "duplicate" def is_false_p(self, bugcrowd_state): - return bugcrowd_state == "not-reproducible" + return bugcrowd_state == "not_reproducible" def is_mitigated(self, bugcrowd_state): return bugcrowd_state == "resolved" @@ -244,7 +268,7 @@ def is_mitigated(self, bugcrowd_state): def is_out_of_scope(self, bugcrowd_state): return bugcrowd_state == "out_of_scope" - def is_risk_accepted(self, bugcrowd_state): + def is_not_applicable(self, bugcrowd_state): return bugcrowd_state == "not_applicable" def is_verified(self, bugcrowd_state): diff --git a/dojo/tools/dependency_track/parser.py b/dojo/tools/dependency_track/parser.py index c564268e2fb..965e3e32362 100644 --- a/dojo/tools/dependency_track/parser.py +++ b/dojo/tools/dependency_track/parser.py @@ -201,8 +201,8 @@ def _convert_dependency_track_finding_to_dojo_finding(self, dependency_track_fin dependency_track_severity = dependency_track_finding['vulnerability']['severity'] vulnerability_severity = self._convert_dependency_track_severity_to_dojo_severity(dependency_track_severity) if vulnerability_severity is None: - logger.warning("Detected severity of %s that could not be mapped for %s. Defaulting to Critical!", dependency_track_severity, title) - vulnerability_severity = "Critical" + logger.warning("Detected severity of %s that could not be mapped for %s. Defaulting to Informational!", dependency_track_severity, title) + vulnerability_severity = "Informational" # Get the cvss score of the vulnerabililty cvss_score = dependency_track_finding['vulnerability'].get("cvssV3BaseScore") diff --git a/dojo/tools/trivy_operator/parser.py b/dojo/tools/trivy_operator/parser.py index 4e1cadda7a0..28037949b85 100644 --- a/dojo/tools/trivy_operator/parser.py +++ b/dojo/tools/trivy_operator/parser.py @@ -78,6 +78,36 @@ def get_findings(self, scan_file, test): package_name = vulnerability.get("resource") package_version = vulnerability.get("installedVersion") cvssv3_score = vulnerability.get("score") + + finding_tags = list() + target_target = None + target_class = None + package_path = None + + if vulnerability.get("packageType"): + package_type = vulnerability.get("packageType") + finding_tags.append(package_type) + + if vulnerability.get("class"): + target_class = vulnerability.get("class") + finding_tags.append(target_class) + + if vulnerability.get("packagePath"): + package_path = vulnerability.get("packagePath") + + if vulnerability.get("target"): + target_target = vulnerability.get("target") + + if target_class == "os-pkgs" or target_class == "lang-pkgs": + if package_path: + file_path = package_path + elif target_target: + file_path = target_target + else: + file_path = None + else: + file_path = None + description = DESCRIPTION_TEMPLATE.format( title=vulnerability.get("title"), fixed_version=mitigation ) @@ -101,6 +131,8 @@ def get_findings(self, scan_file, test): static_finding=True, dynamic_finding=False, service=service, + file_path=file_path, + tags=finding_tags, ) if vuln_id: finding.unsaved_vulnerability_ids = [vuln_id] diff --git a/dojo/tools/trufflehog3/parser.py b/dojo/tools/trufflehog3/parser.py index f723da3ff6d..3302af93c83 100644 --- a/dojo/tools/trufflehog3/parser.py +++ b/dojo/tools/trufflehog3/parser.py @@ -156,7 +156,7 @@ def get_finding_current(self, json_data, test, dupes): title=title, test=test, cwe=798, - description=description, + description=description.replace("\x00", "\uFFFD"), severity=severity, mitigation="Secrets and passwords should be stored in a secure vault or secure storage.", impact="This weakness can lead to the exposure of resources or functionality to unintended actors, possibly providing attackers with sensitive information or even execute arbitrary code.", diff --git a/dojo/tools/wazuh/parser.py b/dojo/tools/wazuh/parser.py index b1ea19d836b..d70c9dbded6 100644 --- a/dojo/tools/wazuh/parser.py +++ b/dojo/tools/wazuh/parser.py @@ -1,10 +1,10 @@ +import hashlib import json -from dojo.models import Finding +from dojo.models import Finding, Endpoint class WazuhParser(object): """ - Use Wazuh Vulnerability API to retrieve the findings The vulnerabilities with condition "Package unfixed" are skipped because there is no fix out yet. https://github.com/wazuh/wazuh/issues/14560 """ @@ -18,54 +18,49 @@ def get_label_for_scan_types(self, scan_type): def get_description_for_scan_types(self, scan_type): return "Wazuh" - def get_findings(self, filename, test): - data = json.load(filename) - # Detect duplications - dupes = dict() + def get_findings(self, file, test): + data = json.load(file) - try: - vulnerability = data[next(iter(data.keys()))]["affected_items"] - except (KeyError, StopIteration): - return list() + if not data: + return [] - if vulnerability is None: - return list() + # Detect duplications + dupes = dict() - for item in vulnerability: + # Loop through each element in the list + vulnerabilities = data.get("data", {}).get("affected_items", []) + for item in vulnerabilities: if ( item["condition"] != "Package unfixed" and item["severity"] != "Untriaged" ): - id = item.get("cve") + cve = item.get("cve") package_name = item.get("name") package_version = item.get("version") description = item.get("condition") - if item.get("severity") == "Untriaged": - severity = "Info" - else: - severity = item.get("severity") - if item.get("status") == "VALID": - active = True - else: - active = False + severity = item.get("severity").capitalize() + agent_ip = item.get("agent_ip") links = item.get("external_references") - title = ( - item.get("title") + " (version: " + package_version + ")" - ) - severity = item.get("severity", "info").capitalize() + cvssv3_score = item.get("cvss3_score") + publish_date = item.get("published") + agent_name = item.get("agent_name") + agent_ip = item.get("agent_ip") + detection_time = item.get("detection_time") + if links: - references = "" - for link in links: - references += f"{link}\n" + references = "\n".join(links) else: references = None - if id and id.startswith("CVE"): - vulnerability_id = id - else: - vulnerability_id = None + title = ( + item.get("title") + " (version: " + package_version + ")" + ) - dupe_key = title + if agent_name: + dupe_key = title + cve + agent_name + package_name + package_version + else: + dupe_key = title + cve + package_name + package_version + dupe_key = hashlib.sha256(dupe_key.encode('utf-8')).hexdigest() if dupe_key in dupes: find = dupes[dupe_key] @@ -77,14 +72,25 @@ def get_findings(self, filename, test): test=test, description=description, severity=severity, - active=active, - mitigation="mitigation", references=references, static_finding=True, component_name=package_name, component_version=package_version, + cvssv3_score=cvssv3_score, + publish_date=publish_date, + unique_id_from_tool=dupe_key, + date=detection_time, ) - if vulnerability_id: - find.unsaved_vulnerability_ids = [vulnerability_id] + + # in some cases the agent_ip is not the perfect way on how to identify a host. Thus prefer the agent_name, if existant. + if agent_name: + find.unsaved_endpoints = [Endpoint(host=agent_name)] + elif agent_ip: + find.unsaved_endpoints = [Endpoint(host=agent_ip)] + + if id: + find.unsaved_vulnerability_ids = cve + dupes[dupe_key] = find + return list(dupes.values()) diff --git a/dojo/tools/wfuzz/parser.py b/dojo/tools/wfuzz/parser.py index 271b7d208c0..a19cd869bd8 100644 --- a/dojo/tools/wfuzz/parser.py +++ b/dojo/tools/wfuzz/parser.py @@ -13,10 +13,12 @@ class WFuzzParser(object): # table to match HTTP error code and severity SEVERITY = { "200": "High", - "500": "Low", + "302": "Low", "401": "Medium", - "407": "Medium", "403": "Medium", + "404": "Medium", + "407": "Medium", + "500": "Low" } def get_scan_types(self): diff --git a/dojo/tools/yarn_audit/parser.py b/dojo/tools/yarn_audit/parser.py index 325049dd514..5f0ae8b39a1 100644 --- a/dojo/tools/yarn_audit/parser.py +++ b/dojo/tools/yarn_audit/parser.py @@ -17,15 +17,24 @@ def get_description_for_scan_types(self, scan_type): def get_findings(self, json_output, test): if json_output is None: return list() - tree = (json.loads(line) for line in json_output) - return self.get_items(tree, test) + tree = None + lines = json_output.read() + if isinstance(lines, bytes): + lines = lines.decode("utf-8") # passes in unittests, but would fail in production + if '"type"' in lines: + lines = lines.split('\n') + tree = (json.loads(line) for line in lines if "{" in line) + return self.get_items_yarn(tree, test) + else: + tree = json.loads(lines) + return self.get_items_auditci(tree, test) - def get_items(self, tree, test): + def get_items_yarn(self, tree, test): items = {} for element in tree: if element.get("type") == "auditAdvisory": node = element.get("data").get("advisory") - item = get_item(node, test) + item = self.get_item_yarn(node, test) unique_key = str(node.get("id")) + str(node.get("module_name")) items[unique_key] = item elif element.get("type") == "error": @@ -33,78 +42,134 @@ def get_items(self, tree, test): raise ValueError( "yarn audit report contains errors: %s", error ) - return list(items.values()) + def get_items_auditci(self, tree, test): # https://github.com/DefectDojo/django-DefectDojo/issues/6495 + items = [] + for element in tree.get("advisories"): + findings = "**findings:** " + str(tree.get("advisories").get(element).get("findings")) + metadata = "**metadata:** " + str(tree.get("advisories").get(element).get("metadata")) + vulnerable_versions = "**vulnerable_versions:** " + str(tree.get("advisories").get(element).get("vulnerable_versions")) + github_advisory_id = "**github_advisory_id:** " + str(tree.get("advisories").get(element).get("github_advisory_id")) + access = "**access:** " + str(tree.get("advisories").get(element).get("access")) + patched_versions = "**patched_versions:** " + str(tree.get("advisories").get(element).get("patched_versions")) + cvss = "**cvss:** " + str(tree.get("advisories").get(element).get("cvss")) + found_by = "**found_by:** " + str(tree.get("advisories").get(element).get("found_by")) + deleted = "**deleted:** " + str(tree.get("advisories").get(element).get("deleted")) + id = "**id:** " + str(tree.get("advisories").get(element).get("id")) + references = "**references:** " + str(tree.get("advisories").get(element).get("references")) + created = "**created:** " + str(tree.get("advisories").get(element).get("created")) + reported_by = "**reported_by:** " + str(tree.get("advisories").get(element).get("reported_by")) + title = "**title:** " + str(tree.get("advisories").get(element).get("title")) + npm_advisory_id = "**npm_advisory_id:** " + str(tree.get("advisories").get(element).get("npm_advisory_id")) + overview = "**overview:** " + str(tree.get("advisories").get(element).get("overview")) + url = "**url:** " + str(tree.get("advisories").get(element).get("url")) + description = "" + description += findings + "\n" + description += metadata + "\n" + description += vulnerable_versions + "\n" + description += github_advisory_id + "\n" + description += access + "\n" + description += patched_versions + "\n" + description += cvss + "\n" + description += found_by + "\n" + description += deleted + "\n" + description += id + "\n" + description += created + "\n" + description += reported_by + "\n" + description += title + "\n" + description += npm_advisory_id + "\n" + description += overview + "\n" + dojo_finding = Finding( + title=tree.get("advisories").get(element).get("cves")[0] + "_" + tree.get("advisories").get(element).get("module_name"), + test=test, + severity=self.severitytranslator(severity=tree.get("advisories").get(element).get("severity")), + description=description, + cve=tree.get("advisories").get(element).get("cves")[0], + mitigation=tree.get("advisories").get(element).get("recommendation"), + references=url + "\n" + references, + component_name=tree.get("advisories").get(element).get("module_name"), + component_version=tree.get("advisories").get(element).get("findings")[0]["version"], + false_p=False, + duplicate=False, + out_of_scope=False, + mitigated=None, + impact="No impact provided", + static_finding=True, + dynamic_finding=False, + ) + if tree.get("advisories").get(element).get("cwe") != []: + dojo_finding.cwe = tree.get("advisories").get(element).get("cwe")[0].strip("CWE-") + items.append(dojo_finding) + return items -def get_item(item_node, test): - if item_node["severity"] == "low": - severity = "Low" - elif item_node["severity"] == "moderate": - severity = "Medium" - elif item_node["severity"] == "high": - severity = "High" - elif item_node["severity"] == "critical": - severity = "Critical" - else: - severity = "Info" + def severitytranslator(self, severity): + if severity == "low": + severity = "Low" + elif severity == "moderate": + severity = "Medium" + elif severity == "high": + severity = "High" + elif severity == "critical": + severity = "Critical" + else: + severity = "Info" + return severity - paths = "" - for finding in item_node["findings"]: - paths += ( - "\n - " - + str(finding["version"]) - + ":" - + str(",".join(finding["paths"][:25])) + def get_item_yarn(self, item_node, test): + severity = self.severitytranslator(severity=item_node["severity"]) + paths = "" + for finding in item_node["findings"]: + paths += ( + "\n - " + + str(finding["version"]) + + ":" + + str(",".join(finding["paths"][:25])) + ) + if len(finding["paths"]) > 25: + paths += "\n - ..... (list of paths truncated after 25 paths)" + cwe = get_npm_cwe(item_node) + dojo_finding = Finding( + title=item_node["title"] + + " - " + + "(" + + item_node["module_name"] + + ", " + + item_node["vulnerable_versions"] + + ")", + test=test, + severity=severity, + file_path=item_node["findings"][0]["paths"][0], + description=item_node["url"] + + "\n" + + item_node["overview"] + + "\n Vulnerable Module: " + + item_node["module_name"] + + "\n Vulnerable Versions: " + + str(item_node["vulnerable_versions"]) + + "\n Patched Version: " + + str(item_node["patched_versions"]) + + "\n Vulnerable Paths: " + + str(paths) + + "\n CWE: " + + str(item_node["cwe"]) + + "\n Access: " + + str(item_node["access"]), + cwe=cwe, + mitigation=item_node["recommendation"], + references=item_node["url"], + component_name=item_node["module_name"], + component_version=item_node["findings"][0]["version"], + false_p=False, + duplicate=False, + out_of_scope=False, + mitigated=None, + impact="No impact provided", + static_finding=True, + dynamic_finding=False, ) - if len(finding["paths"]) > 25: - paths += "\n - ..... (list of paths truncated after 25 paths)" - - cwe = get_npm_cwe(item_node) - - dojo_finding = Finding( - title=item_node["title"] - + " - " - + "(" - + item_node["module_name"] - + ", " - + item_node["vulnerable_versions"] - + ")", - test=test, - severity=severity, - file_path=item_node["findings"][0]["paths"][0], - description=item_node["url"] - + "\n" - + item_node["overview"] - + "\n Vulnerable Module: " - + item_node["module_name"] - + "\n Vulnerable Versions: " - + str(item_node["vulnerable_versions"]) - + "\n Patched Version: " - + str(item_node["patched_versions"]) - + "\n Vulnerable Paths: " - + str(paths) - + "\n CWE: " - + str(item_node["cwe"]) - + "\n Access: " - + str(item_node["access"]), - cwe=cwe, - mitigation=item_node["recommendation"], - references=item_node["url"], - component_name=item_node["module_name"], - component_version=item_node["findings"][0]["version"], - false_p=False, - duplicate=False, - out_of_scope=False, - mitigated=None, - impact="No impact provided", - static_finding=True, - dynamic_finding=False, - ) - - if len(item_node["cves"]) > 0: - dojo_finding.unsaved_vulnerability_ids = list() - for vulnerability_id in item_node["cves"]: - dojo_finding.unsaved_vulnerability_ids.append(vulnerability_id) - - return dojo_finding + if len(item_node["cves"]) > 0: + dojo_finding.unsaved_vulnerability_ids = list() + for vulnerability_id in item_node["cves"]: + dojo_finding.unsaved_vulnerability_ids.append(vulnerability_id) + return dojo_finding diff --git a/dojo/utils.py b/dojo/utils.py index eac65b08a47..135d341e54f 100644 --- a/dojo/utils.py +++ b/dojo/utils.py @@ -1547,7 +1547,7 @@ def calculate_grade(product, *args, **kwargs): grade_product = "grade_product(%s, %s, %s, %s)" % ( critical, high, medium, low) product.prod_numeric_grade = aeval(grade_product) - product.save() + super(Product, product).save() def get_celery_worker_status(): diff --git a/helm/defectdojo/Chart.yaml b/helm/defectdojo/Chart.yaml index c723762ee51..53bce7bc759 100644 --- a/helm/defectdojo/Chart.yaml +++ b/helm/defectdojo/Chart.yaml @@ -1,8 +1,8 @@ apiVersion: v2 -appVersion: "2.31.0-dev" +appVersion: "2.32.0-dev" description: A Helm chart for Kubernetes to install DefectDojo name: defectdojo -version: 1.6.108-dev +version: 1.6.109-dev icon: https://www.defectdojo.org/img/favicon.ico maintainers: - name: madchap diff --git a/pyproject.toml b/pyproject.toml index 9207b1fce1d..37097131172 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,12 +2,12 @@ # Enable the pycodestyle (`E`) and Pyflakes (`F`) rules by default. # Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or # McCabe complexity (`C901`) by default. - select = ["E", "F"] - ignore = ["E501", "E722", "E402", "E731", "E713", "F821", "F601", "F403"] + lint.select = ["E", "F"] + lint.ignore = ["E501", "E722", "E402", "E731", "E713", "F821", "F601", "F403"] # Allow autofix for all enabled rules (when `--fix`) is provided. - fixable = ["ALL"] - unfixable = [] + lint.fixable = ["ALL"] + lint.unfixable = [] # Exclude a variety of commonly ignored directories. exclude = [ @@ -35,10 +35,10 @@ # Not for the dojo specific stuff "dojo/db_migrations" ] - per-file-ignores = {} + lint.per-file-ignores = {} # Same as Black. line-length = 120 # Allow unused variables when underscore-prefixed. - dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" \ No newline at end of file + lint.dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" diff --git a/requirements-lint.txt b/requirements-lint.txt index 4c4b9dda191..96ae82b23ab 100644 --- a/requirements-lint.txt +++ b/requirements-lint.txt @@ -1 +1 @@ -ruff==0.1.15 \ No newline at end of file +ruff==0.2.1 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 75e16b58db2..e7821926bba 100644 --- a/requirements.txt +++ b/requirements.txt @@ -40,7 +40,7 @@ Pillow==10.2.0 # required by django-imagekit psycopg2-binary==2.9.9 cryptography==42.0.2 python-dateutil==2.8.2 -pytz==2023.4 +pytz==2024.1 redis==5.0.1 requests==2.31.0 sqlalchemy==2.0.25 # Required by Celery broker transport @@ -62,7 +62,7 @@ packageurl-python==0.13.4 django-crum==0.7.9 JSON-log-formatter==0.5.2 django-split-settings==1.2.0 -django-debug-toolbar==4.2.0 +django-debug-toolbar==4.3.0 django-debug-toolbar-request-history==0.1.4 vcrpy==6.0.1 vcrpy-unittest==0.1.7 @@ -79,7 +79,7 @@ django-ratelimit==4.1.0 argon2-cffi==23.1.0 blackduck==1.1.0 pycurl==7.45.2 # Required for Celery Broker AWS (SQS) support -boto3==1.34.32 # Required for Celery Broker AWS (SQS) support +boto3==1.34.35 # Required for Celery Broker AWS (SQS) support netaddr==0.10.1 -vulners==2.1.2 +vulners==2.1.5 fontawesomefree==6.5.1 diff --git a/tests/Import_scanner_test.py b/tests/Import_scanner_test.py index f0d1eb6d537..fd5ee3af0b6 100644 --- a/tests/Import_scanner_test.py +++ b/tests/Import_scanner_test.py @@ -17,7 +17,7 @@ def setUp(self): if os.path.isdir(self.repo_path): shutil.rmtree(self.repo_path) os.mkdir(self.repo_path) - scan_types = git.Repo.clone_from('https://github.com/DefectDojo/sample-scan-files', self.repo_path) + git.Repo.clone_from('https://github.com/DefectDojo/sample-scan-files', self.repo_path) self.remove_items = ['__init__.py', '__init__.pyc', 'factory.py', 'factory.pyc', 'factory.py', 'LICENSE', 'README.md', '.gitignore', '.git', '__pycache__'] tool_path = dir_path[:-5] + 'dojo/tools' diff --git a/tests/base_test_class.py b/tests/base_test_class.py index b9ccf352905..b5d041d349e 100644 --- a/tests/base_test_class.py +++ b/tests/base_test_class.py @@ -90,6 +90,14 @@ def setUpClass(cls): print( "starting chromedriver with options: ", vars(dd_driver_options), desired ) + + # TODO - this filter needs to be removed + import warnings + warnings.filterwarnings("ignore", message="executable_path has been deprecated, please pass in a Service object") + warnings.filterwarnings("ignore", message="use options instead of chrome_options") + warnings.filterwarnings("ignore", message="desired_capabilities has been deprecated, please pass in a Service object") + warnings.filterwarnings("ignore", message="It is deprecated to return a value that is not None from a test case") + dd_driver = webdriver.Chrome( os.environ["CHROMEDRIVER"], chrome_options=dd_driver_options, diff --git a/tests/close_old_findings_dedupe_test.py b/tests/close_old_findings_dedupe_test.py index ae0e3dafc3a..08d9b462ae1 100644 --- a/tests/close_old_findings_dedupe_test.py +++ b/tests/close_old_findings_dedupe_test.py @@ -26,12 +26,11 @@ class CloseOldDedupeTest(BaseTestCase): # -------------------------------------------------------------------------------------------------------- def setUp(self): super().setUp() - self.relative_path = dir_path = os.path.dirname(os.path.realpath(__file__)) + self.relative_path = os.path.dirname(os.path.realpath(__file__)) def check_nb_duplicates(self, expected_number_of_duplicates): logger.debug("checking duplicates...") driver = self.driver - retries = 0 for i in range(0, 18): time.sleep(5) # wait bit for celery dedupe task which can be slow on travis self.goto_all_findings_list(driver) diff --git a/tests/close_old_findings_test.py b/tests/close_old_findings_test.py index b776c892181..c2b2fb4c47c 100644 --- a/tests/close_old_findings_test.py +++ b/tests/close_old_findings_test.py @@ -24,7 +24,7 @@ class CloseOldTest(BaseTestCase): # -------------------------------------------------------------------------------------------------------- def setUp(self): super().setUp() - self.relative_path = dir_path = os.path.dirname(os.path.realpath(__file__)) + self.relative_path = os.path.dirname(os.path.realpath(__file__)) @on_exception_html_source_logger def test_delete_findings(self): diff --git a/tests/dedupe_test.py b/tests/dedupe_test.py index 1a75f34f2a4..1199159dba2 100644 --- a/tests/dedupe_test.py +++ b/tests/dedupe_test.py @@ -24,12 +24,11 @@ class DedupeTest(BaseTestCase): # -------------------------------------------------------------------------------------------------------- def setUp(self): super().setUp() - self.relative_path = dir_path = os.path.dirname(os.path.realpath(__file__)) + self.relative_path = os.path.dirname(os.path.realpath(__file__)) def check_nb_duplicates(self, expected_number_of_duplicates): logger.debug("checking duplicates...") driver = self.driver - retries = 0 for i in range(0, 18): time.sleep(5) # wait bit for celery dedupe task which can be slow on travis self.goto_all_findings_list(driver) diff --git a/tests/false_positive_history_test.py b/tests/false_positive_history_test.py index 5b0b36a244d..5e37022b73e 100644 --- a/tests/false_positive_history_test.py +++ b/tests/false_positive_history_test.py @@ -96,7 +96,6 @@ def bulk_edit(self, finding_url, status_id): driver.find_element(By.CSS_SELECTOR, "input[type='submit']").click() def test_retroactive_edit_finding(self): - driver = self.driver # Create two equal findings on different engagements finding_1 = self.create_finding( product_name='QA Test', @@ -125,7 +124,6 @@ def test_retroactive_edit_finding(self): self.assert_is_active(finding_2) def test_retroactive_bulk_edit_finding(self): - driver = self.driver # Create two equal findings on different engagements finding_1 = self.create_finding( product_name='QA Test', diff --git a/tests/finding_test.py b/tests/finding_test.py index 57f512362c7..d9e305b8b3e 100644 --- a/tests/finding_test.py +++ b/tests/finding_test.py @@ -312,7 +312,7 @@ def test_simple_accept_finding(self): # Select and click on the particular finding to edit driver.find_element(By.LINK_TEXT, "App Vulnerable to XSS").click() # Get the status of the current endpoint - pre_status = driver.find_element(By.XPATH, '//*[@id="vuln_endpoints"]/tbody/tr/td[3]').text + driver.find_element(By.XPATH, '//*[@id="vuln_endpoints"]/tbody/tr/td[3]').text # Click on the 'dropdownMenu1 button' driver.find_element(By.ID, "dropdownMenu1").click() # Click on `Close Finding` @@ -336,7 +336,7 @@ def test_unaccept_finding(self): # Select and click on the particular finding to edit driver.find_element(By.LINK_TEXT, "App Vulnerable to XSS").click() # Get the status of the current endpoint - pre_status = driver.find_element(By.XPATH, '//*[@id="remd_endpoints"]/tbody/tr/td[3]').text + driver.find_element(By.XPATH, '//*[@id="remd_endpoints"]/tbody/tr/td[3]').text # Click on the 'dropdownMenu1 button' driver.find_element(By.ID, "dropdownMenu1").click() # Click on `Close Finding` diff --git a/tests/report_builder_test.py b/tests/report_builder_test.py index eaa0cae3db0..c7eca1dc3b1 100644 --- a/tests/report_builder_test.py +++ b/tests/report_builder_test.py @@ -31,7 +31,7 @@ def enter_values(self, driver): for field in inputs: field.send_keys('cover words') if 'wysiwyg-content' in class_names: - content = widget.find_element(By.CLASS_NAME, "editor").send_keys('wysiwyg') + widget.find_element(By.CLASS_NAME, "editor").send_keys('wysiwyg') def generate_HTML_report(self): driver = self.driver diff --git a/tests/search_test.py b/tests/search_test.py index 37f1fe0d1ff..c48a2fa71ac 100644 --- a/tests/search_test.py +++ b/tests/search_test.py @@ -6,9 +6,6 @@ class SearchTests(BaseTestCase): - def test_login(self): - driver = self.driver - def test_search(self): # very basic search test to see if it doesn't 500 driver = self.goto_some_page() diff --git a/tests/zap.py b/tests/zap.py index 8fe45611ca5..18a5d7b6f8a 100755 --- a/tests/zap.py +++ b/tests/zap.py @@ -19,7 +19,7 @@ class Main: try: s.connect((address, port)) - except socket.error as e: + except socket.error: print("Error connecting to ZAP, exiting.") sys.exit(0) diff --git a/unittests/authorization/test_authorization_tags.py b/unittests/authorization/test_authorization_tags.py index 5c98971aff0..0ee3a31f3c1 100644 --- a/unittests/authorization/test_authorization_tags.py +++ b/unittests/authorization/test_authorization_tags.py @@ -46,7 +46,7 @@ def test_has_object_permission_has_permission(self, mock_current_user, mock_has_ def test_has_object_permission_wrong_permission(self): with self.assertRaises(KeyError): - result = has_object_permission(self.product_type, 'Test') + has_object_permission(self.product_type, 'Test') @patch('dojo.templatetags.authorization_tags.configuration_permission') @patch('crum.get_current_user') diff --git a/unittests/dojo_test_case.py b/unittests/dojo_test_case.py index c5165febba8..f6f08679b42 100644 --- a/unittests/dojo_test_case.py +++ b/unittests/dojo_test_case.py @@ -18,7 +18,7 @@ from dojo.models import (SEVERITIES, DojoMeta, Endpoint, Endpoint_Status, Engagement, Finding, JIRA_Issue, JIRA_Project, Notes, Product, Product_Type, System_Settings, Test, - Test_Type, User) + SLA_Configuration, Test_Type, User) logger = logging.getLogger(__name__) @@ -53,6 +53,11 @@ def create_product_type(self, name, *args, description='dummy description', **kw product_type.save() return product_type + def create_sla_configuration(self, name, *args, description='dummy description', critical=7, high=30, medium=60, low=120, **kwargs): + sla_configuration = SLA_Configuration(name=name, description=description, critical=critical, high=high, medium=medium, low=low) + sla_configuration.save() + return sla_configuration + def create_product(self, name, *args, description='dummy description', prod_type=None, tags=[], **kwargs): if not prod_type: prod_type = Product_Type.objects.first() @@ -248,7 +253,7 @@ def add_product_jira(self, data, expect_redirect_to=None, expect_200=False): product = Product.objects.get(id=response.url.split('/')[-2]) except: raise ValueError('error parsing id from redirect uri: ' + response.url) - self.assertTrue(response.url == (expect_redirect_to % product.id)) + self.assertEqual(response.url, (expect_redirect_to % product.id)) else: self.assertEqual(response.status_code, 200) @@ -396,12 +401,12 @@ def assert_jira_issue_in_epic(self, finding, engagement, issue_in_epic=True): response = jira._session.get(url).json().get('fields', {}) epic_link = response.get(epic_link_field, None) if epic_id is None and epic_link is None or issue_in_epic: - self.assertTrue(epic_id == epic_link) + self.assertEqual(epic_id, epic_link) else: - self.assertTrue(epic_id != epic_link) + self.assertNotEqual(epic_id, epic_link) def assert_jira_updated_change(self, old, new): - self.assertTrue(old != new) + self.assertNotEqual(old, new) def get_latest_model(self, model): return model.objects.order_by('id').last() diff --git a/unittests/scans/trivy_operator/vulnerabilityreport_extended.json b/unittests/scans/trivy_operator/vulnerabilityreport_extended.json new file mode 100644 index 00000000000..dfa44a0a0bf --- /dev/null +++ b/unittests/scans/trivy_operator/vulnerabilityreport_extended.json @@ -0,0 +1,206 @@ +{ + "kind": "VulnerabilityReport", + "apiVersion": "aquasecurity.github.io/v1alpha1", + "metadata": { + "name": "pod-ubuntu-ubuntu", + "namespace": "lbc", + "uid": "e2c1fa59-051b-479d-ab47-f7bf6e7f858d", + "resourceVersion": "26700784781", + "generation": 1, + "creationTimestamp": "2024-01-23T13:43:55Z", + "labels": { + "resource-spec-hash": "666674544b", + "trivy-operator.container.name": "ubuntu", + "trivy-operator.resource.kind": "Pod", + "trivy-operator.resource.name": "ubuntu", + "trivy-operator.resource.namespace": "lbc" + }, + "annotations": { + "trivy-operator.aquasecurity.github.io/report-ttl": "24h0m0s" + }, + "ownerReferences": [ + { + "apiVersion": "v1", + "kind": "Pod", + "name": "ubuntu", + "uid": "aa8d6ec8-5417-4190-93e9-6d4d78dc8da9", + "controller": true, + "blockOwnerDeletion": false + } + ], + "managedFields": [ + { + "manager": "trivy-operator", + "operation": "Update", + "apiVersion": "aquasecurity.github.io/v1alpha1", + "time": "2024-01-23T13:43:55Z", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:annotations": { + ".": {}, + "f:trivy-operator.aquasecurity.github.io/report-ttl": {} + }, + "f:labels": { + ".": {}, + "f:resource-spec-hash": {}, + "f:trivy-operator.container.name": {}, + "f:trivy-operator.resource.kind": {}, + "f:trivy-operator.resource.name": {}, + "f:trivy-operator.resource.namespace": {} + }, + "f:ownerReferences": { + ".": {}, + "k:{\"uid\":\"aa8d6ec8-5417-4190-93e9-6d4d78dc8da9\"}": {} + } + }, + "f:report": { + ".": {}, + "f:artifact": { + ".": {}, + "f:digest": {}, + "f:repository": {}, + "f:tag": {} + }, + "f:os": { + ".": {}, + "f:family": {}, + "f:name": {} + }, + "f:registry": { + ".": {}, + "f:server": {} + }, + "f:scanner": { + ".": {}, + "f:name": {}, + "f:vendor": {}, + "f:version": {} + }, + "f:summary": { + ".": {}, + "f:criticalCount": {}, + "f:highCount": {}, + "f:lowCount": {}, + "f:mediumCount": {}, + "f:noneCount": {}, + "f:unknownCount": {} + }, + "f:updateTimestamp": {}, + "f:vulnerabilities": {} + } + } + } + ] + }, + "report": { + "updateTimestamp": "2024-01-23T13:43:55Z", + "scanner": { + "name": "Trivy", + "vendor": "Aqua Security", + "version": "0.48.3" + }, + "registry": { + "server": "index.docker.io" + }, + "artifact": { + "repository": "library/ubuntu", + "digest": "sha256:f78909c2b360d866b3220655c0b079838258b8891a12ac25fc670f0cbb54229f", + "tag": "20.04" + }, + "os": { + "family": "ubuntu", + "name": "20.04" + }, + "summary": { + "criticalCount": 0, + "highCount": 0, + "mediumCount": 5, + "lowCount": 0, + "unknownCount": 0, + "noneCount": 0 + }, + "vulnerabilities": [ + { + "vulnerabilityID": "CVE-2024-0553", + "resource": "libgnutls30", + "installedVersion": "3.6.13-2ubuntu1.9", + "fixedVersion": "3.6.13-2ubuntu1.10", + "publishedDate": "2024-01-16T12:15:45Z", + "lastModifiedDate": "2024-01-19T21:15:08Z", + "severity": "MEDIUM", + "title": "gnutls: incomplete fix for CVE-2023-5981", + "primaryLink": "https://avd.aquasec.com/nvd/cve-2024-0553", + "links": [], + "score": 5.9, + "target": "ubuntu:20.04 (ubuntu 20.04)", + "class": "os-pkgs", + "packageType": "ubuntu" + }, + { + "vulnerabilityID": "CVE-2024-22365", + "resource": "libpam-modules", + "installedVersion": "1.3.1-5ubuntu4.6", + "fixedVersion": "1.3.1-5ubuntu4.7", + "publishedDate": "", + "lastModifiedDate": "", + "severity": "MEDIUM", + "title": "pam: allowing unpriledged user to block another user namespace", + "primaryLink": "https://avd.aquasec.com/nvd/cve-2024-22365", + "links": [], + "score": 5.5, + "target": "ubuntu:20.04 (ubuntu 20.04)", + "class": "os-pkgs", + "packageType": "ubuntu" + }, + { + "vulnerabilityID": "CVE-2024-22365", + "resource": "libpam-modules-bin", + "installedVersion": "1.3.1-5ubuntu4.6", + "fixedVersion": "1.3.1-5ubuntu4.7", + "publishedDate": "", + "lastModifiedDate": "", + "severity": "MEDIUM", + "title": "pam: allowing unpriledged user to block another user namespace", + "primaryLink": "https://avd.aquasec.com/nvd/cve-2024-22365", + "links": [], + "score": 5.5, + "target": "ubuntu:20.04 (ubuntu 20.04)", + "class": "os-pkgs", + "packageType": "ubuntu" + }, + { + "vulnerabilityID": "CVE-2024-22365", + "resource": "libpam-runtime", + "installedVersion": "1.3.1-5ubuntu4.6", + "fixedVersion": "1.3.1-5ubuntu4.7", + "publishedDate": "", + "lastModifiedDate": "", + "severity": "MEDIUM", + "title": "pam: allowing unpriledged user to block another user namespace", + "primaryLink": "https://avd.aquasec.com/nvd/cve-2024-22365", + "links": [], + "score": 5.5, + "target": "ubuntu:20.04 (ubuntu 20.04)", + "class": "os-pkgs", + "packageType": "ubuntu" + }, + { + "vulnerabilityID": "CVE-2024-22365", + "resource": "libpam0g", + "installedVersion": "1.3.1-5ubuntu4.6", + "fixedVersion": "1.3.1-5ubuntu4.7", + "publishedDate": "", + "lastModifiedDate": "", + "severity": "MEDIUM", + "title": "pam: allowing unpriledged user to block another user namespace", + "primaryLink": "https://avd.aquasec.com/nvd/cve-2024-22365", + "links": [], + "score": 5.5, + "target": "ubuntu:20.04 (ubuntu 20.04)", + "class": "os-pkgs", + "packageType": "ubuntu" + } + ] + } +} \ No newline at end of file diff --git a/unittests/scans/trufflehog3/issue_6999.json b/unittests/scans/trufflehog3/issue_6999.json new file mode 100644 index 00000000000..b50545134a7 --- /dev/null +++ b/unittests/scans/trufflehog3/issue_6999.json @@ -0,0 +1,21 @@ +[ + { + "rule": { + "id": "high-entropy-based-rules2", + "message": "High Entropy", + "severity": "MEDIUM" + }, + "path": "tests/Services/GoodDrafts/DataSets/4620060807393", + "line": "2", + "secret": "6B760A0478D4EB84EA8CD6202E866A4C", + "context": { + "2": "4620060807393\";}i:24;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:9:\"DATE_TIME\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:12:\"PROD_REGDATE\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:29:\"2020-01-13T13:27:31.000+03:00\";}i:25;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:6:\"STRING\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:17:\"PROD_COVER_EDITOR\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:13:\"4620060809991\";}i:26;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:9:\"DATE_TIME\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:19:\"PROD_COVER_DATEEDIT\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:29:\"2020-01-13T13:34:33.000+03:00\";}i:27;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:6:\"STRING\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:26:\"PROD_CHANGE_VERSION_STATUS\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:4:\"NONE\";}i:28;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:7:\"BOOLEAN\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:19:\"PROD_COVER_HAS_FILE\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";s:3:\"Yes\";s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:1:\"1\";}i:29;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:6:\"STRING\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:15:\"BRAND_OWNER_GLN\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:13:\"4620060809991\";}i:30;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:6:\"STRING\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:16:\"BRAND_OWNER_NAME\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:105:\"\u041e\u0411\u0429\u0415\u0421\u0422\u0412\u041e \u0421 \u041e\u0413\u0420\u0410\u041d\u0418\u0427\u0415\u041d\u041d\u041e\u0419 \u041e\u0422\u0412\u0415\u0422\u0421\u0422\u0412\u0415\u041d\u041d\u041e\u0421\u0422\u042c\u042e \"\u041b\u0415\u0420\u0423\u0410 \u041f\u0410\u0420\u0424\u042e\u041c\u0421\"\";}i:31;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:6:\"STRING\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:14:\"GS1_MEMBER_GLN\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:13:\"4620060809991\";}i:32;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:6:\"STRING\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:15:\"GS1_MEMBER_NAME\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:105:\"\u041e\u0411\u0429\u0415\u0421\u0422\u0412\u041e \u0421 \u041e\u0413\u0420\u0410\u041d\u0418\u0427\u0415\u041d\u041d\u041e\u0419 \u041e\u0422\u0412\u0415\u0422\u0421\u0422\u0412\u0415\u041d\u041d\u041e\u0421\u0422\u042c\u042e \"\u041b\u0415\u0420\u0423\u0410 \u041f\u0410\u0420\u0424\u042e\u041c\u0421\"\";}i:33;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:6:\"STRING\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:16:\"MANUFACTURER_GLN\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:13:\"4620060809991\";}i:34;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:6:\"STRING\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:17:\"MANUFACTURER_NAME\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:105:\"\u041e\u0411\u0429\u0415\u0421\u0422\u0412\u041e \u0421 \u041e\u0413\u0420\u0410\u041d\u0418\u0427\u0415\u041d\u041d\u041e\u0419 \u041e\u0422\u0412\u0415\u0422\u0421\u0422\u0412\u0415\u041d\u041d\u041e\u0421\u0422\u042c\u042e \"\u041b\u0415\u0420\u0423\u0410 \u041f\u0410\u0420\u0424\u042e\u041c\u0421\"\";}i:35;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:7:\"BOOLEAN\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:23:\"PROD_TM_CHANGED_BY_GS46\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";s:3:\"Yes\";s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:1:\"1\";}i:36;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:7:\"BOOLEAN\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:20:\"PROD_COVER_IS_ACTIVE\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";s:3:\"Yes\";s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:1:\"1\";}i:37;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:10:\"DICTIONARY\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:22:\"TOOL_FOR_PROD_ENCODING\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";s:12:\"\u041f\u043e\u0440\u0442\u0430\u043b\";s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:6:\"PORTAL\";}i:38;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:10:\"DICTIONARY\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:17:\"PACK_DATA_QUALITY\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";s:5:\"> 25%\";s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:20:\"DATA_QUALITY_GROUP_B\";}i:39;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:6:\"STRING\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:12:\"PREFIX_OWNER\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:13:\"4620060809991\";}i:40;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:6:\"STRING\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:22:\"PREFIX_OWNER_NAME_CALC\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:105:\"\u041e\u0411\u0429\u0415\u0421\u0422\u0412\u041e \u0421 \u041e\u0413\u0420\u0410\u041d\u0418\u0427\u0415\u041d\u041d\u041e\u0419 \u041e\u0422\u0412\u0415\u0422\u0421\u0422\u0412\u0415\u041d\u041d\u041e\u0421\u0422\u042c\u042e \"\u041b\u0415\u0420\u0423\u0410 \u041f\u0410\u0420\u0424\u042e\u041c\u0421\"\";}i:41;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:6:\"STRING\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:7:\"ENT_INN\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:10:\"4706036599\";}i:42;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";s:24:\"\u0418\u0437\u043c\u0435\u0440\u0435\u043d\u043e \u0432 iLab\";s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:7:\"BOOLEAN\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:14:\"PROD_MEAS_ILAB\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";s:2:\"No\";s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";s:1:\"0\";s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:1:\"0\";}i:43;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";s:32:\"\u041e\u0440\u0438\u0433\u0438\u043d\u0430\u043b\u044c\u043d\u044b\u0439 GTINAPP\";s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:7:\"BOOLEAN\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:12:\"TRUE_GTINAPP\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";s:2:\"No\";s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";s:1:\"0\";s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:1:\"0\";}}}s:59:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000InfoTypeRecords\";O:41:\"Slimex\\Api\\GS1\\Structures\\InfoTypeRecords\":1:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecords\u0000record\";a:9:{i:0;O:40:\"Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\":5:{s:57:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\u0000AttributeGroups\";N;s:57:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\u0000AttributeValues\";O:41:\"Slimex\\Api\\GS1\\Structures\\AttributeValues\":1:{s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeValues\u0000value\";O:38:\"Slimex\\Api\\GS1\\Structures\\ExtAttrValue\":9:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000MultValue\";O:38:\"Slimex\\Api\\GS1\\Structures\\ExtAttrValue\":9:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000MultValue\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrDescr\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrText\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrType\";s:6:\"STRING\";s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000extAttrId\";s:13:\"GDDB_90000506\";s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000descr\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000dictId\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000groupId\";N;s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000value\";s:45:\"\u0410\u0431\u0441\u043e\u043b\u044e\u0442 \u0432 \u043a\u0430\u0436\u0434\u043e\u043c \u0430\u0440\u043e\u043c\u0430\u0442\u0435\";}s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrDescr\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrText\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrType\";s:6:\"STRING\";s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000extAttrId\";s:13:\"GDDB_90000506\";s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000descr\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000dictId\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000groupId\";N;s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000value\";N;}}s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\u0000idRecord\";i:150303082;s:52:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\u0000infoTypeId\";s:16:\"SRC_156_10000365\";s:54:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\u0000intoTypeText\";N;}i:1;O:40:\"Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\":5:{s:57:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\u0000AttributeGroups\";N;s:57:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\u0000AttributeValues\";O:41:\"Slimex\\Api\\GS1\\Structures\\AttributeValues\":1:{s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeValues\u0000value\";a:8:{i:0;O:38:\"Slimex\\Api\\GS1\\Structures\\ExtAttrValue\":9:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000MultValue\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrDescr\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrText\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrType\";s:10:\"DICTIONARY\";s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000extAttrId\";s:12:\"WEB_90000187\";s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000descr\";s:39:\"\u0420\u041e\u0421\u0421\u0418\u0419\u0421\u041a\u0410\u042f \u0424\u0415\u0414\u0415\u0420\u0410\u0426\u0418\u042f\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000dictId\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000groupId\";N;s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000value\";s:2:\"RU\";}i:1;O:38:\"Slimex\\Api\\GS1\\Structures\\ExtAttrValue\":9:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000MultValue\";O:38:\"Slimex\\Api\\GS1\\Structures\\ExtAttrValue\":9:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000MultValue\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrDescr\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrText\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrType\";s:10:\"DICTIONARY\";s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000extAttrId\";s:12:\"WEB_90000850\";s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000descr\";s:39:\"\u0420\u041e\u0421\u0421\u0418\u0419\u0421\u041a\u0410\u042f \u0424\u0415\u0414\u0415\u0420\u0410\u0426\u0418\u042f\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000dictId\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000groupId\";N;s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000value\";s:2:\"RU\";}s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrDescr\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrText\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrType\";s:10:\"DICTIONARY\";s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000extAttrId\";s:12:\"WEB_90000850\";s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000descr\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000dictId\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000groupId\";N;s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000value\";N;}i:2;O:38:\"Slimex\\Api\\GS1\\Structures\\ExtAttrValue\":9:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000MultValue\";O:38:\"Slimex\\Api\\GS1\\Structures\\ExtAttrValue\":9:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000MultValue\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrDescr\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrText\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrType\";s:10:\"DICTIONARY\";s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000extAttrId\";s:12:\"WEB_90000851\";s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000descr\";s:29:\"\u0421\u0410\u041d\u041a\u0422-\u041f\u0415\u0422\u0415\u0420\u0411\u0423\u0420\u0413\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000dictId\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000groupId\";N;s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000value\";s:5:\"SPE\u00a0\";}s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrDescr\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrText\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrType\";s:10:\"DICTIONARY\";s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000extAttrId\";s:12:\"WEB_90000851\";s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000descr\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000dictId\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000groupId\";N;s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000value\";N;}i:3;O:38:\"Slimex\\Api\\GS1\\Structures\\ExtAttrValue\":9:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000MultValue\";O:38:\"Slimex\\Api\\GS1\\Structures\\ExtAttrValue\":9:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000MultValue\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrDescr\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrText\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrType\";s:6:\"STRING\";s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000extAttrId\";s:12:\"WEB_90000192\";s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000descr\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000dictId\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000groupId\";N;s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000value\";s:13:\"4620060809991\";}s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrDescr\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrText\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrType\";s:6:\"STRING\";s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000extAttrId\";s:12:\"WEB_90000192\";s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000descr\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000dictId\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000groupId\";N;s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000value\";N;}i:4;O:38:\"Slimex\\Api\\GS1\\Structures\\ExtAttrValue\":9:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000MultValue\";O:38:\"Slimex\\Api\\GS1\\Structures\\ExtAttrValue\":9:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000MultValue\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrDescr\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrText\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrType\";s:6:\"STRING\";s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000extAttrId\";s:12:\"WEB_90000626\";s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000descr\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000dictId\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000groupId\";N;s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000value\";s:10:\"4706036599\";}s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrDescr\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrText\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrType\";s:6:\"STRING\";s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000extAttrId\";s:12:\"WEB_90000626\";s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000descr\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000dictId\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000groupId\";N;s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000value\";N;}i:5;O:38:\"Slimex\\Api\\GS1\\Structures\\ExtAttrValue\":9:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000MultValue\";O:38:\"Slimex\\Api\\GS1\\Structures\\ExtAttrValue\":9:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000MultValue\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrDescr\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrText\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrType\";s:5:\"FLOAT\";s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000extAttrId\";s:12:\"WEB_90000627\";s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000descr\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000dictId\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000groupId\";N;s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000value\";s:11:\"7.8020102E8\";}s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrDescr\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrText\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrType\";s:5:\"FLOAT\";s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000extAttrId\";s:12:\"WEB_90000627\";s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000descr\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000dictId\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000groupId\";N;s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000value\";N;}i:6;O:38:\"Slimex\\Api\\GS1\\Structures\\ExtAttrValue\":9:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000MultValue\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrDescr\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrText\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrType\";s:6:\"STRING\";s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000extAttrId\";s:12:\"WEB_90000852\";s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000descr\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000dictId\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000groupId\";N;s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000value\";s:39:\"\u0420\u043e\u0441\u0441\u0438\u0439\u0441\u043a\u0430\u044f \u0424\u0435\u0434\u0435\u0440\u0430\u0446\u0438\u044f\";}i:7;O:38:\"Slimex\\Api\\GS1\\Structures\\ExtAttrValue\":9:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000MultValue\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrDescr\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrText\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrType\";s:6:\"STRING\";s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000extAttrId\";s:12:\"WEB_90000188\";s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000descr\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000dictId\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000groupId\";N;s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000value\";s:29:\"\u0421\u0430\u043d\u043a\u0442-\u041f\u0435\u0442\u0435\u0440\u0431\u0443\u0440\u0433\";}}}s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\u0000idRecord\";i:150303082;s:52:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\u0000infoTypeId\";s:16:\"SRC_165_10000365\";s:54:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\u0000intoTypeText\";N;}i:2;O:40:\"Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\":5:{s:57:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\u0000AttributeGroups\";N;s:57:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\u0000AttributeValues\";O:41:\"Slimex\\Api\\GS1\\Structures\\AttributeValues\":1:{s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeValues\u0000value\";a:2:{i:0;O:38:\"Slimex\\Api\\GS1\\Structures\\ExtAttrValue\":9:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000MultValue\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrDescr\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrText\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrType\";s:6:\"STRING\";s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000extAttrId\";s:12:\"WEB_90000151\";s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000descr\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000dictId\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000groupId\";N;s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000value\";s:493:\"\u041d\u0435 \u0440\u0430\u0441\u043f\u044b\u043b\u044f\u0442\u044c \u0432\u0431\u043b\u0438\u0437\u0438 \u043e\u0433\u043d\u044f, \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0432 \u0441\u0443\u0445\u043e\u043c \u043f\u0440\u043e\u0432\u0435\u0442\u0440\u0438\u0432\u0430\u0435\u043c\u043e\u043c \u043f\u043e\u043c\u0435\u0449\u0435\u043d\u0438\u0438 \u0432\u0434\u0430\u043b\u0438 \u043e\u0442 \u0434\u0435\u0442\u0435\u0439, \u0438\u0437\u0431\u0435\u0433\u0430\u0442\u044c \u043f\u043e\u043f\u0430\u0434\u0430\u043d\u0438\u044f \u043f\u0440\u044f\u043c\u044b\u0445 \u0441\u043e\u043b\u043d\u0435\u0447\u043d\u044b\u0445 \u043b\u0443\u0447\u0435\u0439 \u043d\u0430 \u0444\u043b\u0430\u043a\u043e\u043d \u0441 \u0442\u0443\u0430\u043b\u0435\u0442\u043d\u043e\u0439 \u0432\u043e\u0434\u043e\u0439, \u0442\u0430\u043a \u043a\u0430\u043a \u044d\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u043f\u0440\u0438\u0432\u0435\u0441\u0442\u0438 \u043a \u0442\u0440\u0430\u043d\u0441\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u0446\u0432\u0435\u0442\u0430 \u0438 \u0430\u0440\u043e\u043c\u0430\u0442\u0430. \u0418\u0437\u0431\u0435\u0433\u0430\u0442\u044c \u043f\u043e\u043f\u0430\u0434\u0430\u043d\u0438\u044f \u0432 \u043f\u0438\u0449\u0443 - \u043e\u043f\u0430\u0441\u043d\u043e \u0434\u043b\u044f \u0436\u0438\u0437\u043d\u0438 \u0438 \u0437\u0434\u043e\u0440\u043e\u0432\u044c\u044f.\";}i:1;O:38:\"Slimex\\Api\\GS1\\Structures\\ExtAttrValue\":9:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000MultValue\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrDescr\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrText\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrType\";s:6:\"STRING\";s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000extAttrId\";s:12:\"WEB_90000150\";s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000descr\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000dictId\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000groupId\";N;s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000value\";s:57:\"\u041d\u0430\u043d\u0435\u0441\u0442\u0438 \u043d\u0430 \u043a\u043e\u0436\u0443, \u043e\u0434\u0435\u0436\u0434\u0443, \u0432\u043e\u043b\u043e\u0441\u044b.\";}}}s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\u0000idRecord\";i:150303082;s:52:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\u0000infoTypeId\";s:16:\"SRC_161_10000365\";s:54:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\u0000intoTypeText\";N;}i:3;O:40:\"Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\":5:{s:57:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\u0000AttributeGroups\";N;s:57:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\u0000AttributeValues\";O:41:\"Slimex\\Api\\GS1\\Structures\\AttributeValues\":1:{s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeValues\u0000value\";a:4:{i:0;O:38:\"Slimex\\Api\\GS1\\Structures\\ExtAttrValue\":9:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000MultValue\";O:38:\"Slimex\\Api\\GS1\\Structures\\ExtAttrValue\":9:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000MultValue\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrDescr\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrText\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrType\";s:6:\"STRING\";s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000extAttrId\";s:13:\"GDDE_90000106\";s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000descr\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000dictId\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000groupId\";N;s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000value\";s:2:\"25\";}s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrDescr\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrText\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrType\";s:6:\"STRING\";s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000extAttrId\";s:13:\"GDDE_90000106\";s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000descr\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000dictId\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000groupId\";N;s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000value\";N;}i:1;O:38:\"Slimex\\Api\\GS1\\Structures\\ExtAttrValue\":9:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000MultValue\";O:38:\"Slimex\\Api\\GS1\\Structures\\ExtAttrValue\":9:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000MultValue\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrDescr\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrText\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrType\";s:5:\"FLOAT\";s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000extAttrId\";s:13:\"GDDE_90000109\";s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000descr\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000dictId\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000groupId\";N;s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000value\";s:3:\"5.0\";}s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrDescr\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrText\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrType\";s:5:\"FLOAT\";s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000extAttrId\";s:13:\"GDDE_90000109\";s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000descr\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000dictId\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000groupId\";N;s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000value\";N;}i:2;O:38:\"Slimex\\Api\\GS1\\Structures\\ExtAttrValue\":9:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000MultValue\";O:38:\"Slimex\\Api\\GS1\\Structures\\ExtAttrValue\":9:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000MultValue\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrDescr\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrText\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrType\";s:10:\"DICTIONARY\";s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000extAttrId\";s:17:\"GDDE_90000109_MES\";s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000descr\";s:21:\"\u0413\u041e\u0414; \u041b\u0415\u0422 \";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000dictId\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000groupId\";N;s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000value\";s:3:\"ANN\";}s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrDescr\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrText\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrType\";s:10:\"DICTIONARY\";s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000extAttrId\";s:17:\"GDDE_90000109_MES\";s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000descr\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000dictId\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000groupId\";N;s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000value\";N;}i:3;O:38:\"Slimex\\Api\\GS1\\Structures\\ExtAttrValue\":9:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000MultValue\";O:38:\"Slimex\\Api\\GS1\\Structures\\ExtAttrValue\":9:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000MultValue\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrDescr\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrText\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrType\";s:6:\"STRING\";s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000extAttrId\";s:13:\"GDDE_90000105\";s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000descr\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000dictId\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000groupId\";N;s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000value\";s:1:\"5\";}s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrDescr\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrText\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrType\";s:6:\"STRING\";s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000extAttrId\";s:13:\"GDDE_90000105\";s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000descr\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000dictId\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000groupId\";N;s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000value\";N;}}}s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\u0000idRecord\";i:150303082;s:52:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\u0000infoTypeId\";s:16:\"SRC_152_10000365\";s:54:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\u0000intoTypeText\";N;}i:4;O:40:\"Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\":5:{s:57:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\u0000AttributeGroups\";N;s:57:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\u0000AttributeValues\";O:41:\"Slimex\\Api\\GS1\\Structures\\AttributeValues\":1:{s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeValues\u0000value\";O:38:\"Slimex\\Api\\GS1\\Structures\\ExtAttrValue\":9:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000MultValue\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrDescr\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrText\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000attrType\";s:6:\"STRING\";s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000extAttrId\";s:13:\"GDDB_70000164\";s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000descr\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000dictId\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000groupId\";N;s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ExtAttrValue\u0000value\";s:47:\"alcohol denat., parfum, aqua, bht. Min 80% Vol.\";}}s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\u0000idRecord\";i:150303082;s:52:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\u0000infoTypeId\";s:16:\"SRC_150_10000365\";s:54:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\u0000intoTypeText\";N;}i:5;O:40:\"Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\":5:{s:57:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\u0000AttributeGroups\";O:41:\"Slimex\\Api\\GS1\\Structures\\AttributeGroups\":1:{s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroups\u0000group\";a:2:{i:0;O:44:\"Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\":7:{s:58:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000isCollection\";N;s:57:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000description\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000id\";s:4:\"1550\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000intId\";i:1550;s:54:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000longText\";N;s:56:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000isOptional\";N;s:55:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000shortText\";s:27:\"\u041d\u0430\u0431\u043e\u0440/\u041a\u043e\u043c\u043f\u043b\u0435\u043a\u0442\";}i:1;O:44:\"Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\":7:{s:58:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000isCollection\";b:1;s:57:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000description\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000id\";s:4:\"1551\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000intId\";i:1551;s:54:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000longText\";N;s:56:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000isOptional\";N;s:55:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000shortText\";s:16:\"\u0412\u043b\u043e\u0436\u0435\u043d\u0438\u044f\";}}}s:57:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\u0000AttributeValues\";O:41:\"Slimex\\Api\\GS1\\Structures\\AttributeValues\":1:{s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeValues\u0000value\";N;}s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\u0000idRecord\";N;s:52:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\u0000infoTypeId\";s:14:\"SRC_3_10000365\";s:54:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\u0000intoTypeText\";s:31:\"\u041d\u0430\u0431\u043e\u0440\u044b/\u041a\u043e\u043c\u043f\u043b\u0435\u043a\u0442\u044b\";}i:6;O:40:\"Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\":5:{s:57:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\u0000AttributeGroups\";O:41:\"Slimex\\Api\\GS1\\Structures\\AttributeGroups\":1:{s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroups\u0000group\";a:3:{i:0;O:44:\"Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\":7:{s:58:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000isCollection\";b:1;s:57:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000description\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000id\";s:4:\"1491\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000intId\";i:1491;s:54:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000longText\";N;s:56:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000isOptional\";N;s:55:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000shortText\";s:46:\"\u0417\u0430\u044f\u0432\u043b\u0435\u043d\u0438\u044f \u043e \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0438\";}i:1;O:44:\"Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\":7:{s:58:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000isCollection\";b:1;s:57:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000description\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000id\";s:4:\"1418\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000intId\";i:1418;s:54:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000longText\";N;s:56:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000isOptional\";N;s:55:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000shortText\";s:25:\"\u0426\u0435\u043b\u0435\u0432\u043e\u0439 \u0440\u044b\u043d\u043e\u043a\";}i:2;O:44:\"Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\":7:{s:58:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000isCollection\";b:1;s:57:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000description\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000id\";s:4:\"1419\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000intId\";i:1419;s:54:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000longText\";N;s:56:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000isOptional\";N;s:55:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000shortText\";s:45:\"\u0421\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0435 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u0430\u043c\";}}}s:57:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\u0000AttributeValues\";O:41:\"Slimex\\Api\\GS1\\Structures\\AttributeValues\":1:{s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeValues\u0000value\";N;}s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\u0000idRecord\";N;s:52:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\u0000infoTypeId\";s:15:\"SRC_33_10000365\";s:54:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\u0000intoTypeText\";s:25:\"\u0426\u0435\u043b\u0435\u0432\u044b\u0435 \u0440\u044b\u043d\u043a\u0438\";}i:7;O:40:\"Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\":5:{s:57:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\u0000AttributeGroups\";O:41:\"Slimex\\Api\\GS1\\Structures\\AttributeGroups\":1:{s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroups\u0000group\";O:44:\"Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\":7:{s:58:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000isCollection\";N;s:57:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000description\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000id\";s:4:\"1460\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000intId\";i:1460;s:54:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000longText\";N;s:56:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000isOptional\";N;s:55:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000shortText\";s:12:\"\u041f\u0430\u0440\u0442\u0438\u0438\";}}s:57:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\u0000AttributeValues\";O:41:\"Slimex\\Api\\GS1\\Structures\\AttributeValues\":1:{s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeValues\u0000value\";N;}s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\u0000idRecord\";N;s:52:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\u0000infoTypeId\";s:16:\"SRC_163_10000365\";s:54:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\u0000intoTypeText\";s:12:\"\u041f\u0430\u0440\u0442\u0438\u0438\";}i:8;O:40:\"Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\":5:{s:57:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\u0000AttributeGroups\";O:41:\"Slimex\\Api\\GS1\\Structures\\AttributeGroups\":1:{s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroups\u0000group\";a:2:{i:0;O:44:\"Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\":7:{s:58:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000isCollection\";b:1;s:57:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000description\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000id\";s:4:\"1554\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000intId\";i:1554;s:54:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000longText\";N;s:56:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000isOptional\";N;s:55:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000shortText\";s:61:\"\u041d\u043e\u043c\u0435\u0440 \u0440\u0435\u0433\u043b\u0430\u043c\u0435\u043d\u0442\u0430/\u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u0430 \u0434\u0443\u0445\u043e\u0432\";}i:1;O:44:\"Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\":7:{s:58:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000isCollection\";N;s:57:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000description\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000id\";s:4:\"1544\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000intId\";i:1544;s:54:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000longText\";N;s:56:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000isOptional\";N;s:55:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeGroupMeta\u0000shortText\";s:8:\"\u0414\u0443\u0445\u0438\";}}}s:57:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\u0000AttributeValues\";O:41:\"Slimex\\Api\\GS1\\Structures\\AttributeValues\":1:{s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\AttributeValues\u0000value\";N;}s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\u0000idRecord\";N;s:52:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\u0000infoTypeId\";s:16:\"SRC_173_10000365\";s:54:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecord\u0000intoTypeText\";s:8:\"\u0414\u0443\u0445\u0438\";}}}s:53:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000ReqValues\";O:35:\"Slimex\\Api\\GS1\\Structures\\ReqValues\":1:{s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ReqValues\u0000reqValue\";s:13:\"4620060807393\";}s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000result\";O:41:\"Slimex\\Api\\GS1\\Structures\\OperationResult\":12:{s:54:\"\u0000Slimex\\Api\\GS1\\Structures\\OperationResult\u0000CheckResult\";N;s:60:\"\u0000Slimex\\Api\\GS1\\Structures\\OperationResult\u0000DataObjectRecords\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\OperationResult\u0000pubRslt\";N;s:53:\"\u0000Slimex\\Api\\GS1\\Structures\\OperationResult\u0000SubResults\";N;s:55:\"\u0000Slimex\\Api\\GS1\\Structures\\OperationResult\u0000dataObjectId\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\OperationResult\u0000errCode\";i:0;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\OperationResult\u0000errMsg\";s:95:\"The request has been successfully completed and the response is in the body of the SOAP message\";s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\OperationResult\u0000errName\";s:8:\"NO_ERROR\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\OperationResult\u0000gln\";N;s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\OperationResult\u0000idRecord\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\OperationResult\u0000key\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\OperationResult\u0000variant\";N;}s:64:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000SubDataObjectRecords\";O:46:\"Slimex\\Api\\GS1\\Structures\\SubDataObjectRecords\":1:{s:54:\"\u0000Slimex\\Api\\GS1\\Structures\\SubDataObjectRecords\u0000record\";a:6:{i:0;O:42:\"Slimex\\Api\\GS1\\Structures\\DataObjectRecord\":20:{s:59:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000AttributeGroups\";N;s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000baseKey\";s:9:\"150303669\";s:63:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000BaseAttributeValues\";O:45:\"Slimex\\Api\\GS1\\Structures\\BaseAttributeValues\":1:{s:52:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttributeValues\u0000value\";a:8:{i:0;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:6:\"BINARY\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:17:\"PACK_BASE_IMG_IMG\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";s:21:\"LEROY-Green Lilac.jpg\";s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";s:10:\"image/jpeg\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";N;}i:1;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:7:\"BOOLEAN\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:20:\"PACK_BASE_IMG_LNG_RU\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";s:2:\"No\";s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:1:\"0\";}i:2;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:7:\"BOOLEAN\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:20:\"PACK_BASE_IMG_LNG_EN\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";s:2:\"No\";s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:1:\"0\";}i:3;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:6:\"STRING\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:17:\"PACK_BASE_IMG_URL\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:111:\"http://res.cloudinary.com/gs1-russia/image/upload/v1578911436/PACK_BASE_IMG/4620060807386/LEROY-Green-Lilac.jpg\";}i:4;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:6:\"STRING\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:23:\"PACK_BASE_IMG_URL_CLEAN\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:117:\"http://res.cloudinary.com/gs1-russia/image/upload/v1578911437/PACK_BASE_IMG/4620060807386/clean/LEROY-Green-Lilac.jpg\";}i:5;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:6:\"STRING\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:22:\"PACK_BASE_IMG_IMG_HASH\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:32:\"6B760A0478D4EB84EA8CD6202E866A4C\";}i:6;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";s:20:\"\u0423\u043a\u0440\u0430\u0438\u043d\u0441\u043a\u0438\u0439\";s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:7:\"BOOLEAN\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:20:\"PACK_BASE_IMG_LNG_UA\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";s:2:\"No\";s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";s:1:\"0\";s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:1:\"0\";}i:7;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";s:18:\"\u041a\u0430\u0437\u0430\u0445\u0441\u043a\u0438\u0439\";s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:7:\"BOOLEAN\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:20:\"PACK_BASE_IMG_LNG_KZ\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";s:2:\"No\";s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";s:1:\"0\";s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:1:\"0\";}}}s:59:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000InfoTypeRecords\";O:41:\"Slimex\\Api\\GS1\\Structures\\InfoTypeRecords\":1:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecords\u0000record\";N;}s:53:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000ReqValues\";O:35:\"Slimex\\Api\\GS1\\Structures\\ReqValues\":1:{s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ReqValues\u0000reqValue\";N;}s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000result\";N;s:64:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000SubDataObjectRecords\";O:46:\"Slimex\\Api\\GS1\\Structures\\SubDataObjectRecords\":1:{s:54:\"\u0000Slimex\\Api\\GS1\\Structures\\SubDataObjectRecords\u0000record\";N;}s:59:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000dataObjectDescr\";N;s:56:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000dataObjectId\";s:13:\"PACK_BASE_IMG\";s:58:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000dataObjectText\";N;s:56:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000externalKey1\";N;s:56:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000externalKey2\";N;s:52:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000idRecord\";i:150303669;s:63:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000metaInfoVersionTime\";N;s:58:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000parentIdRecord\";i:150303082;s:53:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000parentKey\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000src\";s:7:\"GS46NEW\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000errors\";N;s:55:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000isValidated\";b:0;}i:1;O:42:\"Slimex\\Api\\GS1\\Structures\\DataObjectRecord\":20:{s:59:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000AttributeGroups\";N;s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000baseKey\";s:9:\"150303089\";s:63:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000BaseAttributeValues\";O:45:\"Slimex\\Api\\GS1\\Structures\\BaseAttributeValues\":1:{s:52:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttributeValues\u0000value\";a:10:{i:0;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:5:\"FLOAT\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:11:\"PACK_HEIGHT\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:3:\"126\";}i:1;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:5:\"FLOAT\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:10:\"PACK_WIDTH\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:2:\"85\";}i:2;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:5:\"FLOAT\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:11:\"PACK_LENGTH\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:2:\"36\";}i:3;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:10:\"DICTIONARY\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:9:\"PACK_MEAS\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";s:2:\"MM\";s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:3:\"MMT\";}i:4;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:5:\"FLOAT\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:11:\"PACK_WEIGHT\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:3:\"225\";}i:5;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:10:\"DICTIONARY\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:23:\"PACK_WEIGHT_MEAS_BRUTTO\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";s:2:\"\u0413\";s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:3:\"GRM\";}i:6;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:5:\"FLOAT\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:17:\"PACK_WEIGHT_NETTO\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:2:\"50\";}i:7;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:10:\"DICTIONARY\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:16:\"PACK_WEIGHT_MEAS\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";s:4:\"\u041c\u041b\";s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:3:\"MLT\";}i:8;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:5:\"FLOAT\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:8:\"PACK_VOL\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:6:\"385560\";}i:9;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:6:\"STRING\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:16:\"PACK_VOL_MEASURE\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:5:\"\u041c\u041c3\";}}}s:59:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000InfoTypeRecords\";O:41:\"Slimex\\Api\\GS1\\Structures\\InfoTypeRecords\":1:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecords\u0000record\";N;}s:53:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000ReqValues\";O:35:\"Slimex\\Api\\GS1\\Structures\\ReqValues\":1:{s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ReqValues\u0000reqValue\";N;}s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000result\";N;s:64:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000SubDataObjectRecords\";O:46:\"Slimex\\Api\\GS1\\Structures\\SubDataObjectRecords\":1:{s:54:\"\u0000Slimex\\Api\\GS1\\Structures\\SubDataObjectRecords\u0000record\";N;}s:59:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000dataObjectDescr\";N;s:56:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000dataObjectId\";s:14:\"PACK_BASE_MEAS\";s:58:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000dataObjectText\";N;s:56:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000externalKey1\";N;s:56:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000externalKey2\";N;s:52:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000idRecord\";i:150303089;s:63:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000metaInfoVersionTime\";N;s:58:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000parentIdRecord\";i:150303082;s:53:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000parentKey\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000src\";s:7:\"GS46NEW\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000errors\";N;s:55:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000isValidated\";b:0;}i:2;O:42:\"Slimex\\Api\\GS1\\Structures\\DataObjectRecord\":20:{s:59:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000AttributeGroups\";N;s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000baseKey\";s:13:\"4620060807393\";s:63:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000BaseAttributeValues\";O:45:\"Slimex\\Api\\GS1\\Structures\\BaseAttributeValues\":1:{s:52:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttributeValues\u0000value\";a:27:{i:0;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:6:\"STRING\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:18:\"PROD_CVR_BASE_GTIN\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:13:\"4620060807386\";}i:1;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:10:\"DICTIONARY\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:14:\"PROD_CODE_TYPE\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";s:6:\"EAN-13\";s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:5:\"EAN13\";}i:2;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:6:\"STRING\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:15:\"PROD_COVER_GTIN\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:13:\"4620060807393\";}i:3;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:6:\"STRING\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:17:\"PROD_COVER_PREFIX\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:9:\"462006080\";}i:4;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:6:\"STRING\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:9:\"PROD_GTIN\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:3:\"739\";}i:5;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:6:\"STRING\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:16:\"PROD_COVER_CHECK\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:1:\"3\";}i:6;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:6:\"STRING\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:8:\"PROD_PVN\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:1:\"0\";}i:7;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:10:\"DICTIONARY\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:20:\"PROD_COVER_TYPE_DICT\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";s:23:\"\u041a\u041e\u0420\u041e\u0411\u041a\u0410/\u0411\u041e\u041a\u0421\";s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:3:\"BOX\";}i:8;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:10:\"DICTIONARY\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:19:\"PROD_COVER_MATERIAL\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";s:12:\"\u041a\u0410\u0420\u0422\u041e\u041d\";s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:2:\"34\";}i:9;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:10:\"DICTIONARY\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:14:\"PROD_PACK_FORM\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";s:26:\"\u041f\u0420\u042f\u041c\u041e\u0423\u0413\u041e\u041b\u042c\u041d\u0418\u041a\";s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:11:\"RECTANGULAR\";}i:10;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:5:\"FLOAT\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:14:\"PROD_COUNT_PCK\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:1:\"5\";}i:11;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:5:\"FLOAT\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:10:\"PROD_COUNT\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:1:\"5\";}i:12;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:10:\"DICTIONARY\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:12:\"PROD_MEASURE\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";s:4:\"\u0428\u0422\";s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:3:\"PCE\";}i:13;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:6:\"STRING\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:17:\"PROD_COVER_AUTHOR\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:13:\"4620060809991\";}i:14;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:9:\"DATE_TIME\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:12:\"PROD_REGDATE\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:29:\"2020-01-13T13:27:31.000+03:00\";}i:15;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:6:\"STRING\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:17:\"PROD_COVER_EDITOR\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:13:\"4620060809991\";}i:16;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:9:\"DATE_TIME\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:19:\"PROD_COVER_DATEEDIT\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:29:\"2020-01-13T13:27:31.000+03:00\";}i:17;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:7:\"BOOLEAN\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:14:\"PROD_HAS_CHVER\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";s:2:\"No\";s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:1:\"0\";}i:18;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:6:\"STRING\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:26:\"PROD_CHANGE_VERSION_STATUS\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:4:\"NONE\";}i:19;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:7:\"BOOLEAN\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:23:\"PROD_TM_CHANGED_BY_GS46\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";s:3:\"Yes\";s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:1:\"1\";}i:20;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:6:\"STRING\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:12:\"PROD_ENT_GLN\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:13:\"4620060809991\";}i:21;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:6:\"STRING\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:13:\"PROD_ENT_NAME\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:105:\"\u041e\u0411\u0429\u0415\u0421\u0422\u0412\u041e \u0421 \u041e\u0413\u0420\u0410\u041d\u0418\u0427\u0415\u041d\u041d\u041e\u0419 \u041e\u0422\u0412\u0415\u0422\u0421\u0422\u0412\u0415\u041d\u041d\u041e\u0421\u0422\u042c\u042e \"\u041b\u0415\u0420\u0423\u0410 \u041f\u0410\u0420\u0424\u042e\u041c\u0421\"\";}i:22;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:7:\"BOOLEAN\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:20:\"PROD_COVER_IS_ACTIVE\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";s:3:\"Yes\";s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:1:\"1\";}i:23;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:10:\"DICTIONARY\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:22:\"TOOL_FOR_PROD_ENCODING\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";s:12:\"\u041f\u043e\u0440\u0442\u0430\u043b\";s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:6:\"PORTAL\";}i:24;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:6:\"STRING\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:16:\"MANUFACTURER_GLN\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:13:\"4620060809991\";}i:25;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";s:33:\"\u0417\u0430\u043a\u043e\u0434\u0438\u0440\u043e\u0432\u0430\u043d\u043e \u0438\u0437 1\u0421\";s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:7:\"BOOLEAN\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:18:\"PROD_CODED_FROM_1C\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";s:2:\"No\";s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";s:1:\"0\";s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:1:\"0\";}i:26;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";s:32:\"\u041e\u0440\u0438\u0433\u0438\u043d\u0430\u043b\u044c\u043d\u044b\u0439 GTINAPP\";s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:7:\"BOOLEAN\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:12:\"TRUE_GTINAPP\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";s:2:\"No\";s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";s:1:\"0\";s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:1:\"0\";}}}s:59:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000InfoTypeRecords\";O:41:\"Slimex\\Api\\GS1\\Structures\\InfoTypeRecords\":1:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecords\u0000record\";N;}s:53:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000ReqValues\";O:35:\"Slimex\\Api\\GS1\\Structures\\ReqValues\":1:{s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ReqValues\u0000reqValue\";N;}s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000result\";N;s:64:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000SubDataObjectRecords\";O:46:\"Slimex\\Api\\GS1\\Structures\\SubDataObjectRecords\":1:{s:54:\"\u0000Slimex\\Api\\GS1\\Structures\\SubDataObjectRecords\u0000record\";O:42:\"Slimex\\Api\\GS1\\Structures\\DataObjectRecord\":20:{s:59:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000AttributeGroups\";N;s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000baseKey\";s:9:\"150303085\";s:63:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000BaseAttributeValues\";O:45:\"Slimex\\Api\\GS1\\Structures\\BaseAttributeValues\":1:{s:52:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttributeValues\u0000value\";a:11:{i:0;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:5:\"FLOAT\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:11:\"PACK_HEIGHT\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:3:\"118\";}i:1;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:5:\"FLOAT\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:10:\"PACK_WIDTH\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:2:\"80\";}i:2;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:5:\"FLOAT\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:11:\"PACK_LENGTH\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:3:\"187\";}i:3;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:10:\"DICTIONARY\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:9:\"PACK_MEAS\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";s:2:\"MM\";s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:3:\"MMT\";}i:4;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:5:\"FLOAT\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:11:\"PACK_WEIGHT\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:3:\"835\";}i:5;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:10:\"DICTIONARY\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:23:\"PACK_WEIGHT_MEAS_BRUTTO\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";s:2:\"\u0413\";s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:3:\"GRM\";}i:6;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:5:\"FLOAT\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:17:\"PACK_WEIGHT_NETTO\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:3:\"800\";}i:7;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:10:\"DICTIONARY\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:16:\"PACK_WEIGHT_MEAS\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";s:2:\"\u0413\";s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:3:\"GRM\";}i:8;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:5:\"FLOAT\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:17:\"PACK_WEIGHT_COVER\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:2:\"35\";}i:9;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:5:\"FLOAT\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:8:\"PACK_VOL\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:7:\"1765280\";}i:10;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:6:\"STRING\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:16:\"PACK_VOL_MEASURE\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:5:\"\u041c\u041c3\";}}}s:59:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000InfoTypeRecords\";O:41:\"Slimex\\Api\\GS1\\Structures\\InfoTypeRecords\":1:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecords\u0000record\";N;}s:53:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000ReqValues\";O:35:\"Slimex\\Api\\GS1\\Structures\\ReqValues\":1:{s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ReqValues\u0000reqValue\";N;}s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000result\";N;s:64:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000SubDataObjectRecords\";O:46:\"Slimex\\Api\\GS1\\Structures\\SubDataObjectRecords\":1:{s:54:\"\u0000Slimex\\Api\\GS1\\Structures\\SubDataObjectRecords\u0000record\";N;}s:59:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000dataObjectDescr\";N;s:56:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000dataObjectId\";s:15:\"PACK_GROUP_MEAS\";s:58:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000dataObjectText\";N;s:56:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000externalKey1\";N;s:56:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000externalKey2\";N;s:52:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000idRecord\";i:150303085;s:63:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000metaInfoVersionTime\";N;s:58:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000parentIdRecord\";i:150303084;s:53:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000parentKey\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000src\";s:7:\"GS46NEW\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000errors\";N;s:55:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000isValidated\";b:0;}}s:59:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000dataObjectDescr\";N;s:56:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000dataObjectId\";s:15:\"PACK_GROUP_UNIT\";s:58:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000dataObjectText\";N;s:56:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000externalKey1\";N;s:56:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000externalKey2\";N;s:52:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000idRecord\";i:150303084;s:63:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000metaInfoVersionTime\";N;s:58:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000parentIdRecord\";i:150303082;s:53:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000parentKey\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000src\";s:7:\"GS46NEW\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000variant\";s:1:\"0\";s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000errors\";N;s:55:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000isValidated\";b:0;}i:3;O:42:\"Slimex\\Api\\GS1\\Structures\\DataObjectRecord\":20:{s:59:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000AttributeGroups\";N;s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000baseKey\";s:9:\"150303087\";s:63:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000BaseAttributeValues\";O:45:\"Slimex\\Api\\GS1\\Structures\\BaseAttributeValues\":1:{s:52:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttributeValues\u0000value\";a:17:{i:0;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:7:\"INTEGER\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:14:\"CERT_ID_RECORD\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:9:\"150303087\";}i:1;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:6:\"STRING\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:9:\"CERT_GTIN\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:13:\"4620060807386\";}i:2;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:10:\"DICTIONARY\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:17:\"PROD_CERT_COUNTRY\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";s:12:\"\u0420\u043e\u0441\u0441\u0438\u044f\";s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:2:\"RU\";}i:3;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:13:\"SUBDICTIONARY\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:14:\"PROD_CERT_TYPE\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";s:52:\"\u0414\u0435\u043a\u043b\u0430\u0440\u0430\u0446\u0438\u044f EAC - \u0432\u0430\u0440\u0438\u0430\u043d\u0442 \u0415\u0410\u042d\u0421 N\";s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:10:\"DECL_EAC_2\";}i:4;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:6:\"STRING\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:16:\"PROD_CERT_NUMBER\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:35:\"\u0415\u0410\u042d\u0421 N RU \u0414-RU.\u0410\u041477.\u0412.06841\";}i:5;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:6:\"BINARY\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:14:\"PROD_CERT_SCAN\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";s:17:\"\u0415\u0410\u042d\u0421-2018.PDF\";s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";s:15:\"application/pdf\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";N;}i:6;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:10:\"DICTIONARY\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:17:\"PROD_CERT_RD_TYPE\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";s:10:\"\u0421\u0435\u0440\u0438\u044f\";s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:9:\"RD_SERIAL\";}i:7;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:4:\"DATE\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:20:\"PROD_CERT_ISSUE_DATE\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:29:\"2018-06-26T12:00:00.000+03:00\";}i:8;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:4:\"DATE\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:23:\"PROD_CERT_VALIDITY_FROM\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:29:\"2018-06-26T12:00:00.000+03:00\";}i:9;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:4:\"DATE\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:21:\"PROD_CERT_VALIDITY_TO\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:29:\"2023-06-25T12:00:00.000+03:00\";}i:10;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:7:\"BOOLEAN\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:26:\"PROD_CERT_VALIDITY_WITHOUT\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";s:2:\"No\";s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:1:\"0\";}i:11;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:6:\"STRING\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:13:\"PROD_CERT_URL\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:100:\"http://res.cloudinary.com/gs1-russia/image/upload/v1578911437/PROD_CERT/4620060807386/EAEHS-2018.pdf\";}i:12;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:6:\"STRING\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:19:\"PROD_CERT_URL_CLEAN\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:100:\"http://res.cloudinary.com/gs1-russia/image/upload/v1578911437/PROD_CERT/4620060807386/EAEHS-2018.pdf\";}i:13;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:9:\"DATE_TIME\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:21:\"PROD_CERT_DATE_CREATE\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:29:\"2020-01-13T13:27:34.000+03:00\";}i:14;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:9:\"DATE_TIME\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:11:\"CHANGE_TIME\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:29:\"2020-01-13T13:34:24.000+03:00\";}i:15;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:7:\"BOOLEAN\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:13:\"PROD_CERT_CHK\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";s:2:\"No\";s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:1:\"0\";}i:16;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:7:\"BOOLEAN\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:18:\"PROD_CERT_PASS_CHK\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";s:2:\"No\";s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:1:\"0\";}}}s:59:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000InfoTypeRecords\";O:41:\"Slimex\\Api\\GS1\\Structures\\InfoTypeRecords\":1:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecords\u0000record\";N;}s:53:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000ReqValues\";O:35:\"Slimex\\Api\\GS1\\Structures\\ReqValues\":1:{s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ReqValues\u0000reqValue\";N;}s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000result\";N;s:64:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000SubDataObjectRecords\";O:46:\"Slimex\\Api\\GS1\\Structures\\SubDataObjectRecords\":1:{s:54:\"\u0000Slimex\\Api\\GS1\\Structures\\SubDataObjectRecords\u0000record\";N;}s:59:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000dataObjectDescr\";N;s:56:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000dataObjectId\";s:9:\"PROD_CERT\";s:58:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000dataObjectText\";N;s:56:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000externalKey1\";N;s:56:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000externalKey2\";N;s:52:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000idRecord\";i:150303087;s:63:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000metaInfoVersionTime\";N;s:58:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000parentIdRecord\";i:150303082;s:53:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000parentKey\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000src\";s:7:\"GS46NEW\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000errors\";N;s:55:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000isValidated\";b:0;}i:4;O:42:\"Slimex\\Api\\GS1\\Structures\\DataObjectRecord\":20:{s:59:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000AttributeGroups\";N;s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000baseKey\";s:9:\"150303083\";s:63:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000BaseAttributeValues\";O:45:\"Slimex\\Api\\GS1\\Structures\\BaseAttributeValues\":1:{s:52:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttributeValues\u0000value\";a:9:{i:0;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:10:\"DICTIONARY\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:7:\"TNVED_1\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";s:146:\"33 - \u042d\u0424\u0418\u0420\u041d\u042b\u0415 \u041c\u0410\u0421\u041b\u0410 \u0418 \u0420\u0415\u0417\u0418\u041d\u041e\u0418\u0414\u042b; \u041f\u0410\u0420\u0424\u042e\u041c\u0415\u0420\u041d\u042b\u0415, \u041a\u041e\u0421\u041c\u0415\u0422\u0418\u0427\u0415\u0421\u041a\u0418\u0415\u0418\u041b\u0418 \u0422\u0423\u0410\u041b\u0415\u0422\u041d\u042b\u0415 \u0421\u0420\u0415\u0414\u0421\u0422\u0412\u0410\";s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:2:\"33\";}i:1;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:13:\"SUBDICTIONARY\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:7:\"TNVED_2\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";s:49:\"330300 - \u0414\u0443\u0445\u0438 \u0438 \u0442\u0443\u0430\u043b\u0435\u0442\u043d\u0430\u044f \u0432\u043e\u0434\u0430:\";s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:6:\"330300\";}i:2;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:13:\"SUBDICTIONARY\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:7:\"TNVED_3\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";s:42:\"3303009000 - - \u0442\u0443\u0430\u043b\u0435\u0442\u043d\u0430\u044f \u0432\u043e\u0434\u0430\";s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:10:\"3303009000\";}i:3;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:10:\"DICTIONARY\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:16:\"PROD_OKPD2_CLASS\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";s:83:\"20 - \u0412\u0435\u0449\u0435\u0441\u0442\u0432\u0430 \u0445\u0438\u043c\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u0438 \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u044b \u0445\u0438\u043c\u0438\u0447\u0435\u0441\u043a\u0438\u0435\";s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:2:\"20\";}i:4;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:13:\"SUBDICTIONARY\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:19:\"PROD_OKPD2_SUBCLASS\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";s:178:\"20.4 - \u041c\u044b\u043b\u043e \u0438 \u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0430 \u043c\u043e\u044e\u0449\u0438\u0435, \u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0430 \u0447\u0438\u0441\u0442\u044f\u0449\u0438\u0435 \u0438 \u043f\u043e\u043b\u0438\u0440\u0443\u044e\u0449\u0438\u0435, \u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0430 \u043f\u0430\u0440\u0444\u044e\u043c\u0435\u0440\u043d\u044b\u0435 \u0438 \u043a\u043e\u0441\u043c\u0435\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0435\";s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:4:\"20.4\";}i:5;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:13:\"SUBDICTIONARY\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:16:\"PROD_OKPD2_GROUP\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";s:77:\"20.42 - \u0421\u0440\u0435\u0434\u0441\u0442\u0432\u0430 \u043f\u0430\u0440\u0444\u044e\u043c\u0435\u0440\u043d\u044b\u0435 \u0438 \u043a\u043e\u0441\u043c\u0435\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0435\";s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:5:\"20.42\";}i:6;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:13:\"SUBDICTIONARY\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:19:\"PROD_OKPD2_SUBGROUP\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";s:79:\"20.42.1 - \u0421\u0440\u0435\u0434\u0441\u0442\u0432\u0430 \u043f\u0430\u0440\u0444\u044e\u043c\u0435\u0440\u043d\u044b\u0435 \u0438 \u043a\u043e\u0441\u043c\u0435\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0435\";s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:7:\"20.42.1\";}i:7;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:13:\"SUBDICTIONARY\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:15:\"PROD_OKPD2_KIND\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";s:50:\"20.42.11 - \u0414\u0443\u0445\u0438 \u0438 \u0442\u0443\u0430\u043b\u0435\u0442\u043d\u0430\u044f \u0432\u043e\u0434\u0430\";s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:8:\"20.42.11\";}i:8;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:13:\"SUBDICTIONARY\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:14:\"PROD_OKPD2_CAT\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";s:42:\"20.42.11.120 - \u0412\u043e\u0434\u0430 \u0442\u0443\u0430\u043b\u0435\u0442\u043d\u0430\u044f\";s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:12:\"20.42.11.120\";}}}s:59:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000InfoTypeRecords\";O:41:\"Slimex\\Api\\GS1\\Structures\\InfoTypeRecords\":1:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecords\u0000record\";N;}s:53:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000ReqValues\";O:35:\"Slimex\\Api\\GS1\\Structures\\ReqValues\":1:{s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ReqValues\u0000reqValue\";N;}s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000result\";N;s:64:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000SubDataObjectRecords\";O:46:\"Slimex\\Api\\GS1\\Structures\\SubDataObjectRecords\":1:{s:54:\"\u0000Slimex\\Api\\GS1\\Structures\\SubDataObjectRecords\u0000record\";N;}s:59:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000dataObjectDescr\";N;s:56:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000dataObjectId\";s:10:\"PROD_CLASS\";s:58:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000dataObjectText\";N;s:56:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000externalKey1\";N;s:56:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000externalKey2\";N;s:52:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000idRecord\";i:150303083;s:63:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000metaInfoVersionTime\";N;s:58:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000parentIdRecord\";i:150303082;s:53:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000parentKey\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000src\";s:7:\"GS46NEW\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000errors\";N;s:55:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000isValidated\";b:0;}i:5;O:42:\"Slimex\\Api\\GS1\\Structures\\DataObjectRecord\":20:{s:59:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000AttributeGroups\";N;s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000baseKey\";s:9:\"150303088\";s:63:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000BaseAttributeValues\";O:45:\"Slimex\\Api\\GS1\\Structures\\BaseAttributeValues\":1:{s:52:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttributeValues\u0000value\";a:3:{i:0;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:10:\"DICTIONARY\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:7:\"TNVED_1\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";s:146:\"33 - \u042d\u0424\u0418\u0420\u041d\u042b\u0415 \u041c\u0410\u0421\u041b\u0410 \u0418 \u0420\u0415\u0417\u0418\u041d\u041e\u0418\u0414\u042b; \u041f\u0410\u0420\u0424\u042e\u041c\u0415\u0420\u041d\u042b\u0415, \u041a\u041e\u0421\u041c\u0415\u0422\u0418\u0427\u0415\u0421\u041a\u0418\u0415\u0418\u041b\u0418 \u0422\u0423\u0410\u041b\u0415\u0422\u041d\u042b\u0415 \u0421\u0420\u0415\u0414\u0421\u0422\u0412\u0410\";s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:2:\"33\";}i:1;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:13:\"SUBDICTIONARY\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:7:\"TNVED_2\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";s:49:\"330300 - \u0414\u0443\u0445\u0438 \u0438 \u0442\u0443\u0430\u043b\u0435\u0442\u043d\u0430\u044f \u0432\u043e\u0434\u0430:\";s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:6:\"330300\";}i:2;O:39:\"Slimex\\Api\\GS1\\Structures\\BaseAttrValue\":12:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileData\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrDescr\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrText\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000attrType\";s:13:\"SUBDICTIONARY\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000baseAttrId\";s:7:\"TNVED_3\";s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000descr\";s:42:\"3303009000 - - \u0442\u0443\u0430\u043b\u0435\u0442\u043d\u0430\u044f \u0432\u043e\u0434\u0430\";s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000dictId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000fileName\";N;s:48:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000groupId\";N;s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000mimeType\";N;s:46:\"\u0000Slimex\\Api\\GS1\\Structures\\BaseAttrValue\u0000value\";s:10:\"3303009000\";}}}s:59:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000InfoTypeRecords\";O:41:\"Slimex\\Api\\GS1\\Structures\\InfoTypeRecords\":1:{s:49:\"\u0000Slimex\\Api\\GS1\\Structures\\InfoTypeRecords\u0000record\";N;}s:53:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000ReqValues\";O:35:\"Slimex\\Api\\GS1\\Structures\\ReqValues\":1:{s:45:\"\u0000Slimex\\Api\\GS1\\Structures\\ReqValues\u0000reqValue\";N;}s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000result\";N;s:64:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000SubDataObjectRecords\";O:46:\"Slimex\\Api\\GS1\\Structures\\SubDataObjectRecords\":1:{s:54:\"\u0000Slimex\\Api\\GS1\\Structures\\SubDataObjectRecords\u0000record\";N;}s:59:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000dataObjectDescr\";N;s:56:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000dataObjectId\";s:5:\"TNVED\";s:58:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000dataObjectText\";N;s:56:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000externalKey1\";N;s:56:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000externalKey2\";N;s:52:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000idRecord\";i:150303088;s:63:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000metaInfoVersionTime\";N;s:58:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000parentIdRecord\";i:150303082;s:53:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000parentKey\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000src\";s:7:\"GS46NEW\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000variant\";N;s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000errors\";N;s:55:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000isValidated\";b:0;}}}s:59:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000dataObjectDescr\";N;s:56:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000dataObjectId\";s:14:\"PACK_BASE_UNIT\";s:58:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000dataObjectText\";N;s:56:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000externalKey1\";N;s:56:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000externalKey2\";N;s:52:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000idRecord\";i:150303082;s:63:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000metaInfoVersionTime\";N;s:58:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000parentIdRecord\";i:135989643;s:53:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000parentKey\";N;s:47:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000src\";s:7:\"GS46NEW\";s:51:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000variant\";s:1:\"0\";s:50:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000errors\";N;s:55:\"\u0000Slimex\\Api\\GS1\\Structures\\DataObjectRecord\u0000isValidated\";b:0;}" + }, + "id": "848440d9-fecb-38ce-8527-3a7b2db07b99", + "branch": null, + "message": null, + "author": null, + "commit": null, + "date": null + } + ] \ No newline at end of file diff --git a/unittests/scans/wazuh/one_finding_with_endpoint.json b/unittests/scans/wazuh/one_finding_with_endpoint.json new file mode 100644 index 00000000000..5363d0f13ed --- /dev/null +++ b/unittests/scans/wazuh/one_finding_with_endpoint.json @@ -0,0 +1,29 @@ +{ + "data": { + "affected_items": [ + { + "name": "asdf", + "version": "1", + "cve": "CVE-1234-1234", + "cvss2_score": 0, + "title": "CVE-1234-1234 affects curl", + "published": "2023-12-07", + "architecture": "amd64", + "status": "VALID", + "cvss3_score": 6.5, + "external_references": [ + "https://nvd.nist.gov/vuln/detail/CVE-1234-1234", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-1234-1234" + ], + "updated": "2023-12-24", + "severity": "Medium", + "type": "PACKAGE", + "detection_time": "2023-12-13T22:11:57+00:00", + "condition": "Package less than 2", + "agent_ip": "111.111.111.111", + "agent_name": "agent-1" + } + ], + "total_affected_items": 1 + } +} \ No newline at end of file diff --git a/unittests/scans/wfuzz/issue_7863.json b/unittests/scans/wfuzz/issue_7863.json new file mode 100644 index 00000000000..e98b8ad9f08 --- /dev/null +++ b/unittests/scans/wfuzz/issue_7863.json @@ -0,0 +1,14 @@ +[ + { + "chars": 2823, + "code": 404, + "payload": "/server-status | GET /server-status HTTP/1.1\nContent-Type: application/x-www-form-urlencoded\nUser-Agent: Wfuzz/3.1.0\nHost: example.com\n\n", + "lines": 0, + "location": "", + "method": "GET", + "post_data": [], + "server": "", + "url": "https://example.com/server-status", + "words": 60 + } +] \ No newline at end of file diff --git a/unittests/scans/yarn_audit/issue_6495.json b/unittests/scans/yarn_audit/issue_6495.json new file mode 100644 index 00000000000..f7a594c0eee --- /dev/null +++ b/unittests/scans/yarn_audit/issue_6495.json @@ -0,0 +1,142 @@ +{ + "actions": [], + "advisories": { + "1068298": { + "findings": [ + { + "version": "1.3.5", + "paths": [ + "@angular/cli>ini", + "danger>parse-git-config>ini", + "@datorama/akita>schematics-utilities>@schematics/update>ini", + "@datorama/akita-ng-entity-service>@datorama/akita>schematics-utilities>@schematics/update>ini", + "nodemon>update-notifier>latest-version>package-json>registry-auth-token>rc>ini", + "@mikro-orm/cli>@mikro-orm/migrations>knex>liftoff>findup-sync>resolve-dir>global-modules>global-prefix>ini", + "@mikro-orm/cli>@mikro-orm/knex>@mikro-orm/migrations>knex>liftoff>findup-sync>resolve-dir>global-modules>global-prefix>ini", + "@mikro-orm/cli>@mikro-orm/entity-generator>@mikro-orm/knex>@mikro-orm/migrations>knex>liftoff>findup-sync>resolve-dir>global-modules>global-prefix>ini" + ] + } + ], + "metadata": null, + "vulnerable_versions": "<1.3.6", + "module_name": "ini", + "severity": "high", + "github_advisory_id": "GHSA-qqgx-2p2h-9c37", + "cves": [ + "CVE-2020-7788" + ], + "access": "public", + "patched_versions": ">=1.3.6", + "cvss": { + "score": 7.3, + "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L" + }, + "updated": "2021-07-28T21:12:38.000Z", + "recommendation": "Upgrade to version 1.3.6 or later", + "cwe": [ + "CWE-1321" + ], + "found_by": null, + "deleted": null, + "id": 1068298, + "references": "- https://github.com/npm/ini/commit/56d2805e07ccd94e2ba0984ac9240ff02d44b6f1\n- https://www.npmjs.com/advisories/1589\n- https://snyk.io/vuln/SNYK-JS-INI-1048974\n- https://nvd.nist.gov/vuln/detail/CVE-2020-7788\n- https://lists.debian.org/debian-lts-announce/2020/12/msg00032.html\n- https://github.com/advisories/GHSA-qqgx-2p2h-9c37", + "created": "2020-12-10T16:53:45.000Z", + "reported_by": null, + "title": "Prototype Pollution", + "npm_advisory_id": null, + "overview": "### Overview\nThe `ini` npm package before version 1.3.6 has a Prototype Pollution vulnerability.\n\nIf an attacker submits a malicious INI file to an application that parses it with `ini.parse`, they will pollute the prototype on the application. This can be exploited further depending on the context.\n\n### Patches\n\nThis has been patched in 1.3.6\n\n### Steps to reproduce\n\npayload.ini\n```\n[__proto__]\npolluted = \"polluted\"\n```\n\npoc.js:\n```\nvar fs = require('fs')\nvar ini = require('ini')\n\nvar parsed = ini.parse(fs.readFileSync('./payload.ini', 'utf-8'))\nconsole.log(parsed)\nconsole.log(parsed.__proto__)\nconsole.log(polluted)\n```\n\n```\n> node poc.js\n{}\n{ polluted: 'polluted' }\n{ polluted: 'polluted' }\npolluted\n```", + "url": "https://github.com/advisories/GHSA-qqgx-2p2h-9c37" + }, + "1075625": { + "findings": [ + { + "version": "0.4.3", + "paths": [ + "@playwright/test>jpeg-js", + "@playwright/test>playwright-core>jpeg-js" + ] + } + ], + "metadata": null, + "vulnerable_versions": "<0.4.4", + "module_name": "jpeg-js", + "severity": "high", + "github_advisory_id": "GHSA-xvf7-4v9q-58w6", + "cves": [ + "CVE-2022-25851" + ], + "access": "public", + "patched_versions": ">=0.4.4", + "cvss": { + "score": 7.5, + "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H" + }, + "updated": "2022-06-20T21:58:36.000Z", + "recommendation": "Upgrade to version 0.4.4 or later", + "cwe": [ + "CWE-835" + ], + "found_by": null, + "deleted": null, + "id": 1075625, + "references": "- https://nvd.nist.gov/vuln/detail/CVE-2022-25851\n- https://github.com/jpeg-js/jpeg-js/issues/105\n- https://github.com/jpeg-js/jpeg-js/pull/106/\n- https://github.com/jpeg-js/jpeg-js/commit/9ccd35fb5f55a6c4f1902ac5b0f270f675750c27\n- https://snyk.io/vuln/SNYK-JAVA-ORGWEBJARSNPM-2860295\n- https://snyk.io/vuln/SNYK-JS-JPEGJS-2859218\n- https://github.com/advisories/GHSA-xvf7-4v9q-58w6", + "created": "2022-06-11T00:00:17.000Z", + "reported_by": null, + "title": "Infinite loop in jpeg-js", + "npm_advisory_id": null, + "overview": "The package jpeg-js before 0.4.4 is vulnerable to Denial of Service (DoS) where a particular piece of input will cause the program to enter an infinite loop and never return.", + "url": "https://github.com/advisories/GHSA-xvf7-4v9q-58w6" + }, + "1075701": { + "findings": [ + { + "version": "9.6.0", + "paths": [ + "nodemon>update-notifier>latest-version>package-json>got" + ] + } + ], + "metadata": null, + "vulnerable_versions": "<11.8.5", + "module_name": "got", + "severity": "moderate", + "github_advisory_id": "GHSA-pfrx-2q88-qq97", + "cves": [ + "CVE-2022-33987" + ], + "access": "public", + "patched_versions": ">=11.8.5", + "cvss": { + "score": 0, + "vectorString": null + }, + "updated": "2022-06-27T17:09:23.000Z", + "recommendation": "Upgrade to version 11.8.5 or later", + "cwe": [], + "found_by": null, + "deleted": null, + "id": 1075701, + "references": "- https://nvd.nist.gov/vuln/detail/CVE-2022-33987\n- https://github.com/sindresorhus/got/pull/2047\n- https://github.com/sindresorhus/got/compare/v12.0.3...v12.1.0\n- https://github.com/sindresorhus/got/commit/861ccd9ac2237df762a9e2beed7edd88c60782dc\n- https://github.com/sindresorhus/got/releases/tag/v11.8.5\n- https://github.com/sindresorhus/got/releases/tag/v12.1.0\n- https://github.com/advisories/GHSA-pfrx-2q88-qq97", + "created": "2022-06-19T00:00:21.000Z", + "reported_by": null, + "title": "Got allows a redirect to a UNIX socket", + "npm_advisory_id": null, + "overview": "The got package before 11.8.5 and 12.1.0 for Node.js allows a redirect to a UNIX socket.", + "url": "https://github.com/advisories/GHSA-pfrx-2q88-qq97" + } + }, + "muted": [], + "metadata": { + "vulnerabilities": { + "info": 0, + "low": 0, + "moderate": 1, + "high": 10, + "critical": 0 + }, + "dependencies": 2236, + "devDependencies": 121, + "optionalDependencies": 0, + "totalDependencies": 2357 + } +} \ No newline at end of file diff --git a/unittests/test_apiv2_metadata.py b/unittests/test_apiv2_metadata.py index cedaaeb3574..3e39dc2bbc0 100644 --- a/unittests/test_apiv2_metadata.py +++ b/unittests/test_apiv2_metadata.py @@ -35,7 +35,7 @@ def test_query_metadata(self): def test_query_product_endpoint(self): r = self.client.get(reverse('product-detail', args=(1,))) - self.assertTrue(dict(name='foo', value='bar') in r.json()['product_meta']) + self.assertIn(dict(name='foo', value='bar'), r.json()['product_meta']) def test_delete(self): r = self.client.delete(reverse('metadata-detail', args=(self.mid,))) @@ -45,7 +45,7 @@ def test_delete(self): self.assertEqual(r.status_code, 404) r = self.client.get(reverse('product-detail', args=(1,))) - self.assertTrue(dict(name='foo', value='bar') not in r.json()['product_meta']) + self.assertNotIn(dict(name='foo', value='bar'), r.json()['product_meta']) def test_no_product_or_endpoint_as_parameter(self): r = self.create(name='foo', value='bar') diff --git a/unittests/test_apiv2_methods.py b/unittests/test_apiv2_methods.py deleted file mode 100644 index 4f6694ef30f..00000000000 --- a/unittests/test_apiv2_methods.py +++ /dev/null @@ -1,46 +0,0 @@ -from dojo.urls import v2_api -from .dojo_test_case import DojoTestCase -from .test_rest_framework import get_open_api3_json_schema, BASE_API_URL - - -class ApiEndpointMethods(DojoTestCase): - fixtures = ['dojo_testdata.json'] - - def setUp(self): - super().setUp() - - self.schema = get_open_api3_json_schema() - - self.registry = v2_api.registry - - def test_is_defined(self): - exempt_list = [ - 'import-scan', 'reimport-scan', 'notes', 'system_settings', 'roles', - 'import-languages', 'endpoint_meta_import', 'test_types', - 'configuration_permissions', 'risk_acceptance', 'questionnaire_questions', - 'questionnaire_answers', 'questionnaire_answered_questionnaires', - 'questionnaire_engagement_questionnaires', 'questionnaire_general_questionnaires', - 'dojo_group_members', 'product_members', 'product_groups', 'product_type_groups', - 'product_type_members' - ] - for reg, _, _ in sorted(self.registry): - if reg in exempt_list: - continue - for method in ['get', 'post']: - self.assertIsNotNone( - self.schema["paths"][f'{BASE_API_URL}/{reg}/'].get(method), - f"Endpoint: {reg}, Method: {method}", - ) - - for method in ['get', 'put', 'patch', 'delete']: - self.assertIsNotNone( - self.schema["paths"][f'{BASE_API_URL}/{reg}' + '/{id}/'].get(method), - f"Endpoint: {reg}, Method: {method}", - ) - - self.assertIsNotNone( - self.schema["paths"] - .get(f'{BASE_API_URL}/{reg}' + '/{id}/delete_preview/', {}) - .get('get'), - f"Endpoint: {reg}, Method: get - delete_preview", - ) diff --git a/unittests/test_apiv2_methods_and_endpoints.py b/unittests/test_apiv2_methods_and_endpoints.py new file mode 100644 index 00000000000..408dd7cc060 --- /dev/null +++ b/unittests/test_apiv2_methods_and_endpoints.py @@ -0,0 +1,126 @@ +from dojo.urls import v2_api +from .dojo_test_case import DojoTestCase +from .test_rest_framework import get_open_api3_json_schema, BASE_API_URL +import django.apps +from dojo.api_v2 import serializers +from dojo.models import ( + Contact, + Product_Line, + Report_Type, + CWE, + BurpRawRequestResponse, + FileAccessToken, + UserAnnouncement, + BannerConf, + GITHUB_Conf, + GITHUB_Issue, + GITHUB_Clone, + GITHUB_Details_Cache, + GITHUB_PKey, + Tool_Product_History, + Objects_Review, + Objects_Product, + Testing_Guide_Category, + Testing_Guide, + Benchmark_Type, + Benchmark_Category, + Benchmark_Requirement, + Benchmark_Product, + Benchmark_Product_Summary, + Choice, +) + + +class ApiEndpointMethods(DojoTestCase): + fixtures = ['dojo_testdata.json'] + + def setUp(self): + super().setUp() + + self.schema = get_open_api3_json_schema() + + self.registry = v2_api.registry + + def test_is_defined(self): + exempt_list = [ + 'import-scan', 'reimport-scan', 'notes', 'system_settings', 'roles', + 'import-languages', 'endpoint_meta_import', 'test_types', + 'configuration_permissions', 'questionnaire_questions', + 'questionnaire_answers', 'questionnaire_answered_questionnaires', + 'questionnaire_engagement_questionnaires', 'questionnaire_general_questionnaires', + 'dojo_group_members', 'product_members', 'product_groups', 'product_type_groups', + 'product_type_members' + ] + for reg, _, _ in sorted(self.registry): + if reg in exempt_list: + continue + for method in ['get', 'post']: + self.assertIsNotNone( + self.schema["paths"][f'{BASE_API_URL}/{reg}/'].get(method), + f"Endpoint: {reg}, Method: {method}", + ) + + for method in ['get', 'put', 'patch', 'delete']: + self.assertIsNotNone( + self.schema["paths"][f'{BASE_API_URL}/{reg}' + '/{id}/'].get(method), + f"Endpoint: {reg}, Method: {method}", + ) + + self.assertIsNotNone( + self.schema["paths"] + .get(f'{BASE_API_URL}/{reg}' + '/{id}/delete_preview/', {}) + .get('get'), + f"Endpoint: {reg}, Method: get - delete_preview", + ) + + +class ApiEndpoints(DojoTestCase): + fixtures = ['dojo_testdata.json'] + + def setUp(self): + super().setUp() + + self.used_models = [] + for serializer in serializers.__dict__.values(): + if hasattr(serializer, 'Meta'): + if hasattr(serializer.Meta, 'model'): + self.used_models.append(serializer.Meta.model) + self.no_api_models = [ # TODO: these models are excluded from check for now but implementation is needed + Contact, + Product_Line, + Report_Type, + CWE, + BurpRawRequestResponse, + FileAccessToken, + UserAnnouncement, + BannerConf, + GITHUB_Conf, + GITHUB_Issue, + GITHUB_Clone, + GITHUB_Details_Cache, + GITHUB_PKey, + Tool_Product_History, + Objects_Review, + Objects_Product, + Testing_Guide_Category, + Testing_Guide, + Benchmark_Type, + Benchmark_Category, + Benchmark_Requirement, + Benchmark_Product, + Benchmark_Product_Summary, + Choice, + ] + + def test_is_defined(self): + for subclass in django.apps.apps.get_models(): + if subclass.__module__ == 'dojo.models': + if (subclass.__name__[:9] == "Tagulous_") and (subclass.__name__[-5:] == "_tags"): + continue + if subclass.__name__ in ['Alerts']: + continue + with self.subTest(subclass=subclass): + if subclass in self.used_models: + self.assertNotIn(subclass, self.no_api_models, "Thank you, you just implemented API endpoint for the model which was needed. Please remove it from exception list 'self.no_api_models'") + if subclass not in self.no_api_models: + self.assertIn(subclass, self.used_models, "API endpoint for the managing mentioned model is need") diff --git a/unittests/test_apiv2_scan_import_options.py b/unittests/test_apiv2_scan_import_options.py index d4edb46360c..6e62f460d93 100644 --- a/unittests/test_apiv2_scan_import_options.py +++ b/unittests/test_apiv2_scan_import_options.py @@ -49,11 +49,11 @@ def test_epmty_scan(self): Import the ZAP scan without a test file. """ test = self.import_zap_scan(upload_empty_scan=False) - self.assertFalse(len(self.get_all_finding_ids(active=True, test__test_type=test.test_type)) == 0) + self.assertNotEqual(len(self.get_all_finding_ids(active=True, test__test_type=test.test_type)), 0) def test_full_scan(self): """ Import the ZAP scan with a test file. """ test = self.import_zap_scan(upload_empty_scan=True) - self.assertFalse(len(self.get_all_finding_ids(active=True, test__test_type=test.test_type)) == 0) + self.assertNotEqual(len(self.get_all_finding_ids(active=True, test__test_type=test.test_type)), 0) diff --git a/unittests/test_apiv2_user.py b/unittests/test_apiv2_user.py index ae8fd76b1e6..54f7e391c1f 100644 --- a/unittests/test_apiv2_user.py +++ b/unittests/test_apiv2_user.py @@ -18,7 +18,7 @@ def test_user_list(self): r = self.client.get(reverse('user-list')) self.assertEqual(r.status_code, 200, r.content[:1000]) user_list = r.json()['results'] - self.assertTrue(len(user_list) >= 1, r.content[:1000]) + self.assertGreaterEqual(len(user_list), 1, r.content[:1000]) for user in user_list: for item in ['username', 'first_name', 'last_name', 'email']: self.assertIn(item, user, r.content[:1000]) diff --git a/unittests/test_apply_finding_template.py b/unittests/test_apply_finding_template.py index 7cb89799618..e3302d0df61 100644 --- a/unittests/test_apply_finding_template.py +++ b/unittests/test_apply_finding_template.py @@ -161,7 +161,7 @@ def test_apply_template_to_finding_with_data_saves_success(self): test_mitigation = 'template mitigation' test_impact = 'template impact' - result = self.make_request(True, 1, 1, + self.make_request(True, 1, 1, {'title': test_title, 'cwe': test_cwe, 'severity': test_severity, @@ -191,11 +191,11 @@ def test_unauthorized_apply_template_to_finding_fails(self): def test_apply_template_to_finding_with_illegal_finding_fails(self): with self.assertRaises(Exception): - result = self.make_request(True, None, 1) + self.make_request(True, None, 1) def test_apply_template_to_finding_with_illegal_template_fails(self): with self.assertRaises(Exception): - result = self.make_request(True, 1, None) + self.make_request(True, 1, None) def test_apply_template_to_finding_with_no_data_returns_view_success(self): result = self.make_request(True, 1, 1, None) diff --git a/unittests/test_deduplication_logic.py b/unittests/test_deduplication_logic.py index e42344ad31e..8937104d92d 100644 --- a/unittests/test_deduplication_logic.py +++ b/unittests/test_deduplication_logic.py @@ -214,7 +214,7 @@ def test_identical_except_filepath_legacy(self): finding_new, finding_24 = self.copy_and_reset_finding(id=24) finding_new.file_path = '/dev/null' - finding_22 = Finding.objects.get(id=22) + Finding.objects.get(id=22) finding_new.save(dedupe_option=True) diff --git a/unittests/test_endpoint_meta_import.py b/unittests/test_endpoint_meta_import.py index b7b25542a0f..e0ec437c42f 100644 --- a/unittests/test_endpoint_meta_import.py +++ b/unittests/test_endpoint_meta_import.py @@ -27,7 +27,7 @@ def test_endpoint_meta_import_endpoint_create_tag_create_meta_create(self): meta_count_before = self.db_dojo_meta_count() with assertImportModelsCreated(self, tests=0, engagements=0, products=0, endpoints=3): - import0 = self.endpoint_meta_import_scan_with_params( + self.endpoint_meta_import_scan_with_params( self.meta_import_full, create_endpoints=True, create_tags=True, create_dojo_meta=True) self.assertEqual(endpoint_count_before + 3, self.db_endpoint_count()) @@ -36,20 +36,20 @@ def test_endpoint_meta_import_endpoint_create_tag_create_meta_create(self): def test_endpoint_meta_import_endpoint_missing_hostname(self): with assertImportModelsCreated(self, tests=0, engagements=0, products=0, endpoints=0): - import0 = self.endpoint_meta_import_scan_with_params( + self.endpoint_meta_import_scan_with_params( self.meta_import_no_hostname, create_endpoints=True, create_tags=True, create_dojo_meta=True, expected_http_status_code=400) def test_endpoint_meta_import_tag_remove_column(self): # Import full scan first with assertImportModelsCreated(self, tests=0, engagements=0, products=0, endpoints=3): - import0 = self.endpoint_meta_import_scan_with_params( + self.endpoint_meta_import_scan_with_params( self.meta_import_full, create_endpoints=True, create_tags=True, create_dojo_meta=False) # Record numbers endpoint_count_before = self.db_endpoint_count() endpoint_tag_count_before = self.db_endpoint_tag_count() # Import again with one column missing with assertImportModelsCreated(self, tests=0, engagements=0, products=0, endpoints=0): - import0 = self.endpoint_meta_import_scan_with_params( + self.endpoint_meta_import_scan_with_params( self.meta_import_updated_removed, create_endpoints=True, create_tags=True, create_dojo_meta=False) # See that nothing has been removed self.assertEqual(endpoint_count_before, self.db_endpoint_count()) @@ -58,14 +58,14 @@ def test_endpoint_meta_import_tag_remove_column(self): def test_endpoint_meta_import_tag_added_column(self): # Import full scan first with assertImportModelsCreated(self, tests=0, engagements=0, products=0, endpoints=3): - import0 = self.endpoint_meta_import_scan_with_params( + self.endpoint_meta_import_scan_with_params( self.meta_import_full, create_endpoints=True, create_tags=True, create_dojo_meta=False) # Record numbers endpoint_count_before = self.db_endpoint_count() endpoint_tag_count_before = self.db_endpoint_tag_count() # Import again with one column added with assertImportModelsCreated(self, tests=0, engagements=0, products=0, endpoints=0): - import0 = self.endpoint_meta_import_scan_with_params( + self.endpoint_meta_import_scan_with_params( self.meta_import_updated_added, create_endpoints=True, create_tags=True, create_dojo_meta=False) # See that nothing has been removed self.assertEqual(endpoint_count_before, self.db_endpoint_count()) @@ -75,7 +75,7 @@ def test_endpoint_meta_import_tag_added_column(self): def test_endpoint_meta_import_tag_changed_column(self): # Import full scan first with assertImportModelsCreated(self, tests=0, engagements=0, products=0, endpoints=3): - import0 = self.endpoint_meta_import_scan_with_params( + self.endpoint_meta_import_scan_with_params( self.meta_import_full, create_endpoints=True, create_tags=True, create_dojo_meta=False) # Record numbers endpoint_count_before = self.db_endpoint_count() @@ -85,7 +85,7 @@ def test_endpoint_meta_import_tag_changed_column(self): human_resource_tag = endpoint['tags'][endpoint['tags'].index('team:human resources')] # Import again with one column missing with assertImportModelsCreated(self, tests=0, engagements=0, products=0, endpoints=0): - import0 = self.endpoint_meta_import_scan_with_params( + self.endpoint_meta_import_scan_with_params( self.meta_import_updated_changed, create_endpoints=True, create_tags=True, create_dojo_meta=False) # See that nothing has been added or removed self.assertEqual(endpoint_count_before, self.db_endpoint_count()) @@ -99,14 +99,14 @@ def test_endpoint_meta_import_tag_changed_column(self): def test_endpoint_meta_import_meta_remove_column(self): # Import full scan first with assertImportModelsCreated(self, tests=0, engagements=0, products=0, endpoints=3): - import0 = self.endpoint_meta_import_scan_with_params( + self.endpoint_meta_import_scan_with_params( self.meta_import_full, create_endpoints=True, create_tags=False, create_dojo_meta=True) # Record numbers endpoint_count_before = self.db_endpoint_count() meta_count_before = self.db_dojo_meta_count() # Import again with one column missing with assertImportModelsCreated(self, tests=0, engagements=0, products=0, endpoints=0): - import0 = self.endpoint_meta_import_scan_with_params( + self.endpoint_meta_import_scan_with_params( self.meta_import_updated_removed, create_endpoints=True, create_tags=False, create_dojo_meta=True) # See that nothing has been removed self.assertEqual(endpoint_count_before, self.db_endpoint_count()) @@ -115,14 +115,14 @@ def test_endpoint_meta_import_meta_remove_column(self): def test_endpoint_meta_import_meta_added_column(self): # Import full scan first with assertImportModelsCreated(self, tests=0, engagements=0, products=0, endpoints=3): - import0 = self.endpoint_meta_import_scan_with_params( + self.endpoint_meta_import_scan_with_params( self.meta_import_full, create_endpoints=True, create_tags=False, create_dojo_meta=True) # Record numbers endpoint_count_before = self.db_endpoint_count() meta_count_before = self.db_dojo_meta_count() # Import again with one column added with assertImportModelsCreated(self, tests=0, engagements=0, products=0, endpoints=0): - import0 = self.endpoint_meta_import_scan_with_params( + self.endpoint_meta_import_scan_with_params( self.meta_import_updated_added, create_endpoints=True, create_tags=False, create_dojo_meta=True) # 1 meta x 3 endpoints = 3 tags self.assertEqual(endpoint_count_before, self.db_endpoint_count()) @@ -131,7 +131,7 @@ def test_endpoint_meta_import_meta_added_column(self): def test_endpoint_meta_import_meta_changed_column(self): # Import full scan first with assertImportModelsCreated(self, tests=0, engagements=0, products=0, endpoints=3): - import0 = self.endpoint_meta_import_scan_with_params( + self.endpoint_meta_import_scan_with_params( self.meta_import_full, create_endpoints=True, create_tags=False, create_dojo_meta=True) # Record numbers endpoint_count_before = self.db_endpoint_count() @@ -141,7 +141,7 @@ def test_endpoint_meta_import_meta_changed_column(self): meta_value = self.get_endpoints_meta_api(endpoint_id, 'team')['results'][0]['value'] # Import again with one column missing with assertImportModelsCreated(self, tests=0, engagements=0, products=0, endpoints=0): - import0 = self.endpoint_meta_import_scan_with_params( + self.endpoint_meta_import_scan_with_params( self.meta_import_updated_changed, create_endpoints=True, create_tags=False, create_dojo_meta=True) # See that nothing has been added or removed self.assertEqual(endpoint_count_before, self.db_endpoint_count()) diff --git a/unittests/test_endpoint_model.py b/unittests/test_endpoint_model.py index 35b99c80aab..839a815b166 100644 --- a/unittests/test_endpoint_model.py +++ b/unittests/test_endpoint_model.py @@ -161,9 +161,9 @@ def test_equality_without_products(self): e2 = Endpoint(protocol="https", host="localhost", port=5439, path="test", query="param=value") e3 = Endpoint(protocol="https", host="localhost", port=5439, path="different", query="param=value") # Verify e1 and e2 are actually equal - self.assertTrue(e1 == e2) + self.assertEqual(e1, e2) # Verify e1 and e2 are not equal because the path is different - self.assertFalse(e1 == e3) + self.assertNotEqual(e1, e3) def test_equality_with_one_product_one_without(self): # Define the product @@ -176,7 +176,7 @@ def test_equality_with_one_product_one_without(self): e2 = Endpoint(host="localhost", product=p) # Verify e1 and e2 are actually equal # Since on has a product and the other does not, we cannot use products to aid in equality - self.assertTrue(e1 == e2) + self.assertEqual(e1, e2) def test_equality_with_products(self): # Define the product @@ -196,10 +196,10 @@ def test_equality_with_products(self): e3 = Endpoint(host="localhost", product=p2) # Verify e1 and e2 are actually equal # Since the products match, this should be true - self.assertTrue(e1 == e2) + self.assertEqual(e1, e2) # Verify e1 and e2 are not equal # Because the products are different, the endpoint objects are not the same - self.assertFalse(e1 == e3) + self.assertNotEqual(e1, e3) @skip("Outdated - this class was testing clean-up broken entries in old version of model; new version of model doesn't to store broken entries") diff --git a/unittests/test_factory.py b/unittests/test_factory.py index f5bf436a475..a1e1d20fd69 100644 --- a/unittests/test_factory.py +++ b/unittests/test_factory.py @@ -9,25 +9,25 @@ def test_get_parser(self): scan_type = "Acunetix Scan" testfile = open(get_unit_tests_path() + "/scans/acunetix/one_finding.xml") parser = get_parser(scan_type) - findings = parser.get_findings(testfile, Test()) + parser.get_findings(testfile, Test()) testfile.close() with self.subTest(scan_type="Anchore Engine Scan"): scan_type = "Anchore Engine Scan" testfile = open(get_unit_tests_path() + "/scans/anchore_engine/one_vuln.json") parser = get_parser(scan_type) - findings = parser.get_findings(testfile, Test()) + parser.get_findings(testfile, Test()) testfile.close() with self.subTest(scan_type="Tenable Scan"): scan_type = "Tenable Scan" testfile = open(get_unit_tests_path() + "/scans/tenable/nessus/nessus_v_unknown.xml") parser = get_parser(scan_type) - findings = parser.get_findings(testfile, Test()) + parser.get_findings(testfile, Test()) testfile.close() with self.subTest(scan_type="ZAP Scan"): scan_type = "ZAP Scan" testfile = open(get_unit_tests_path() + "/scans/zap/some_2.9.0.xml") parser = get_parser(scan_type) - findings = parser.get_findings(testfile, Test()) + parser.get_findings(testfile, Test()) testfile.close() def test_get_parser_error(self): diff --git a/unittests/test_finding_model.py b/unittests/test_finding_model.py index ca7494142e5..e6053dcd916 100644 --- a/unittests/test_finding_model.py +++ b/unittests/test_finding_model.py @@ -1,5 +1,7 @@ from .dojo_test_case import DojoTestCase -from dojo.models import Finding, Test, Engagement, DojoMeta +from dojo.models import User, Finding, Test, Engagement, DojoMeta +from datetime import datetime, timedelta +from crum import impersonate class TestFindingModel(DojoTestCase): @@ -262,3 +264,147 @@ def test_get_references_with_links_markdown(self): finding = Finding() finding.references = 'URL: [https://www.example.com](https://www.example.com)' self.assertEqual('URL: [https://www.example.com](https://www.example.com)', finding.get_references_with_links()) + + +class TestFindingSLAExpiration(DojoTestCase): + fixtures = ['dojo_testdata.json'] + + def run(self, result=None): + testuser = User.objects.get(username='admin') + testuser.usercontactinfo.block_execution = True + testuser.save() + + # unit tests are running without any user, which will result in actions like dedupe happening in the celery process + # this doesn't work in unittests as unittests are using an in memory sqlite database and celery can't see the data + # so we're running the test under the admin user context and set block_execution to True + with impersonate(testuser): + super().run(result) + + def test_sla_expiration_date(self): + """ + tests if the SLA expiration date and SLA days remaining are calculated correctly + after a finding's severity is updated + """ + user, _ = User.objects.get_or_create(username='admin') + product_type = self.create_product_type('test_product_type') + sla_config = self.create_sla_configuration(name='test_sla_config') + product = self.create_product(name='test_product', prod_type=product_type) + product.sla_configuration = sla_config + product.save() + engagement = self.create_engagement('test_eng', product) + test = self.create_test(engagement=engagement, scan_type='ZAP Scan', title='test_test') + finding = Finding.objects.create( + test=test, + reporter=user, + title='test_finding', + severity='Critical', + date=datetime.now().date()) + finding.set_sla_expiration_date() + + expected_sla_days = getattr(product.sla_configuration, finding.severity.lower(), None) + self.assertEqual(finding.sla_expiration_date, datetime.now().date() + timedelta(days=expected_sla_days)) + self.assertEqual(finding.sla_days_remaining(), expected_sla_days) + + def test_sla_expiration_date_after_finding_severity_updated(self): + """ + tests if the SLA expiration date and SLA days remaining are calculated correctly + after a finding's severity is updated + """ + user, _ = User.objects.get_or_create(username='admin') + product_type = self.create_product_type('test_product_type') + sla_config = self.create_sla_configuration(name='test_sla_config') + product = self.create_product(name='test_product', prod_type=product_type) + product.sla_configuration = sla_config + product.save() + engagement = self.create_engagement('test_eng', product) + test = self.create_test(engagement=engagement, scan_type='ZAP Scan', title='test_test') + finding = Finding.objects.create( + test=test, + reporter=user, + title='test_finding', + severity='Critical', + date=datetime.now().date()) + finding.set_sla_expiration_date() + + expected_sla_days = getattr(product.sla_configuration, finding.severity.lower(), None) + self.assertEqual(finding.sla_expiration_date, datetime.now().date() + timedelta(days=expected_sla_days)) + self.assertEqual(finding.sla_days_remaining(), expected_sla_days) + + finding.severity = 'Medium' + finding.set_sla_expiration_date() + + expected_sla_days = getattr(product.sla_configuration, finding.severity.lower(), None) + self.assertEqual(finding.sla_expiration_date, datetime.now().date() + timedelta(days=expected_sla_days)) + self.assertEqual(finding.sla_days_remaining(), expected_sla_days) + + def test_sla_expiration_date_after_product_updated(self): + """ + tests if the SLA expiration date and SLA days remaining are calculated correctly + after a product changed from one SLA configuration to another + """ + user, _ = User.objects.get_or_create(username='admin') + product_type = self.create_product_type('test_product_type') + sla_config_1 = self.create_sla_configuration(name='test_sla_config_1') + sla_config_2 = self.create_sla_configuration( + name='test_sla_config_2', + critical=1, + high=2, + medium=3, + low=4) + product = self.create_product(name='test_product', prod_type=product_type) + product.sla_configuration = sla_config_1 + product.save() + engagement = self.create_engagement('test_eng', product) + test = self.create_test(engagement=engagement, scan_type='ZAP Scan', title='test_test') + finding = Finding.objects.create( + test=test, + reporter=user, + title='test_finding', + severity='Critical', + date=datetime.now().date()) + + expected_sla_days = getattr(product.sla_configuration, finding.severity.lower(), None) + self.assertEqual(finding.sla_expiration_date, datetime.now().date() + timedelta(days=expected_sla_days)) + self.assertEqual(finding.sla_days_remaining(), expected_sla_days) + + product.sla_configuration = sla_config_2 + product.save() + + finding.set_sla_expiration_date() + + expected_sla_days = getattr(product.sla_configuration, finding.severity.lower(), None) + self.assertEqual(finding.sla_expiration_date, datetime.now().date() + timedelta(days=expected_sla_days)) + self.assertEqual(finding.sla_days_remaining(), expected_sla_days) + + def test_sla_expiration_date_after_sla_configuration_updated(self): + """ + tests if the SLA expiration date and SLA days remaining are calculated correctly + after the SLA configuration on a product was updated to a different number of SLA days + """ + user, _ = User.objects.get_or_create(username='admin') + product_type = self.create_product_type('test_product_type') + sla_config = self.create_sla_configuration(name='test_sla_config') + product = self.create_product(name='test_product', prod_type=product_type) + product.sla_configuration = sla_config + product.save() + engagement = self.create_engagement('test_eng', product) + test = self.create_test(engagement=engagement, scan_type='ZAP Scan', title='test_test') + finding = Finding.objects.create( + test=test, + reporter=user, + title='test_finding', + severity='Critical', + date=datetime.now().date()) + + expected_sla_days = getattr(product.sla_configuration, finding.severity.lower(), None) + self.assertEqual(finding.sla_expiration_date, datetime.now().date() + timedelta(days=expected_sla_days)) + self.assertEqual(finding.sla_days_remaining(), expected_sla_days) + + sla_config.critical = 10 + sla_config.save() + + finding.set_sla_expiration_date() + + expected_sla_days = getattr(product.sla_configuration, finding.severity.lower(), None) + self.assertEqual(finding.sla_expiration_date, datetime.now().date() + timedelta(days=expected_sla_days)) + self.assertEqual(finding.sla_days_remaining(), expected_sla_days) diff --git a/unittests/test_import_reimport.py b/unittests/test_import_reimport.py index d765377b8f6..92bcb0097d3 100644 --- a/unittests/test_import_reimport.py +++ b/unittests/test_import_reimport.py @@ -714,7 +714,7 @@ def test_import_0_reimport_1_active_not_verified(self): test_id = reimport1['test'] self.assertEqual(test_id, test_id) - test = self.get_test_api(test_id) + self.get_test_api(test_id) findings = self.get_test_findings_api(test_id) self.log_finding_summary_json_api(findings) @@ -754,7 +754,7 @@ def test_import_0_reimport_1_active_verified_reimport_0_active_verified(self): findings = self.get_test_findings_api(test_id) self.log_finding_summary_json_api(findings) - finding_count_before = self.db_finding_count() + self.db_finding_count() endpoint_count_before = self.db_endpoint_count() endpoint_status_count_before_active = self.db_endpoint_status_count(mitigated=False) endpoint_status_count_before_mitigated = self.db_endpoint_status_count(mitigated=True) @@ -770,12 +770,12 @@ def test_import_0_reimport_1_active_verified_reimport_0_active_verified(self): endpoint_status_count_before_mitigated = self.db_endpoint_status_count(mitigated=True) with assertTestImportModelsCreated(self, reimports=1, affected_findings=2, closed=1, reactivated=1, untouched=3): - reimport0 = self.reimport_scan_with_params(test_id, self.zap_sample0_filename) + self.reimport_scan_with_params(test_id, self.zap_sample0_filename) test_id = reimport1['test'] self.assertEqual(test_id, test_id) - test = self.get_test_api(test_id) + self.get_test_api(test_id) findings = self.get_test_findings_api(test_id) self.log_finding_summary_json_api(findings) @@ -928,7 +928,7 @@ def test_import_0_reimport_3_active_verified(self): test_id = reimport1['test'] self.assertEqual(test_id, test_id) - test = self.get_test_api(test_id) + self.get_test_api(test_id) findings = self.get_test_findings_api(test_id) self.log_finding_summary_json_api(findings) self.assert_finding_count_json(4 + 2, findings) @@ -1023,7 +1023,7 @@ def test_import_0_reimport_0_anchore_file_path(self): # reimport exact same report with assertTestImportModelsCreated(self, reimports=1, affected_findings=0, untouched=4): - reimport0 = self.reimport_scan_with_params(test_id, self.anchore_file_name, scan_type=self.scan_type_anchore) + self.reimport_scan_with_params(test_id, self.anchore_file_name, scan_type=self.scan_type_anchore) active_findings_after = self.get_test_findings_api(test_id, active=True) self.log_finding_summary_json_api(active_findings_after) @@ -1152,7 +1152,7 @@ def test_import_6_reimport_6_gitlab_dep_scan_component_name_and_version(self): self.assert_finding_count_json(6, active_findings_before) with assertTestImportModelsCreated(self, reimports=1, affected_findings=0, created=0, untouched=6): - reimport0 = self.reimport_scan_with_params(test_id, + self.reimport_scan_with_params(test_id, self.gitlab_dep_scan_components_filename, scan_type=self.scan_type_gtlab_dep_scan, minimum_severity='Info') @@ -1435,7 +1435,7 @@ def test_import_reimport_vulnerability_ids(self): ) reimport_test.save() - reimport0 = self.reimport_scan_with_params(reimport_test.id, self.anchore_grype_file_name, scan_type=self.anchore_grype_scan_type) + self.reimport_scan_with_params(reimport_test.id, self.anchore_grype_file_name, scan_type=self.anchore_grype_scan_type) findings = Finding.objects.filter(test=reimport_test) self.assertEqual(4, len(findings)) self.assertEqual('GHSA-v6rh-hp5x-86rv', findings[3].cve) diff --git a/unittests/test_importers_importer.py b/unittests/test_importers_importer.py index 6566b7f555c..5318c64e165 100644 --- a/unittests/test_importers_importer.py +++ b/unittests/test_importers_importer.py @@ -208,7 +208,7 @@ def test_import_by_product_name_exists_engagement_name_exists(self, mock): def test_import_by_product_name_exists_engagement_name_not_exists(self): with assertImportModelsCreated(self, tests=0, engagements=0, products=0, product_types=0, endpoints=0): - import0 = self.import_scan_with_params(NPM_AUDIT_NO_VULN_FILENAME, scan_type=NPM_AUDIT_SCAN_TYPE, product_name=PRODUCT_NAME_DEFAULT, + self.import_scan_with_params(NPM_AUDIT_NO_VULN_FILENAME, scan_type=NPM_AUDIT_SCAN_TYPE, product_name=PRODUCT_NAME_DEFAULT, engagement=None, engagement_name=ENGAGEMENT_NAME_NEW, expected_http_status_code=400) @patch('dojo.jira_link.helper.get_jira_project') @@ -227,7 +227,7 @@ def test_import_by_product_name_exists_engagement_name_not_exists_auto_create(se def test_import_by_product_name_not_exists_engagement_name(self): with assertImportModelsCreated(self, tests=0, engagements=0, products=0, product_types=0, endpoints=0): - import0 = self.import_scan_with_params(NPM_AUDIT_NO_VULN_FILENAME, scan_type=NPM_AUDIT_SCAN_TYPE, product_name=PRODUCT_NAME_NEW, + self.import_scan_with_params(NPM_AUDIT_NO_VULN_FILENAME, scan_type=NPM_AUDIT_SCAN_TYPE, product_name=PRODUCT_NAME_NEW, engagement=None, engagement_name=ENGAGEMENT_NAME_NEW, expected_http_status_code=400) @patch('dojo.jira_link.helper.get_jira_project') @@ -259,11 +259,11 @@ def test_import_by_product_type_name_not_exists_product_name_not_exists_engageme def test_endpoint_meta_import_by_product_name_exists(self): with assertImportModelsCreated(self, tests=0, engagements=0, products=0, endpoints=0): - import0 = self.endpoint_meta_import_scan_with_params(ENDPOINT_META_IMPORTER_FILENAME, product=None, product_name=PRODUCT_NAME_DEFAULT, expected_http_status_code=201) + self.endpoint_meta_import_scan_with_params(ENDPOINT_META_IMPORTER_FILENAME, product=None, product_name=PRODUCT_NAME_DEFAULT, expected_http_status_code=201) def test_endpoint_meta_import_by_product_name_not_exists(self): with assertImportModelsCreated(self, tests=0, engagements=0, products=0, endpoints=0): - import0 = self.endpoint_meta_import_scan_with_params(ENDPOINT_META_IMPORTER_FILENAME, product=None, product_name=PRODUCT_NAME_NEW, expected_http_status_code=400) + self.endpoint_meta_import_scan_with_params(ENDPOINT_META_IMPORTER_FILENAME, product=None, product_name=PRODUCT_NAME_NEW, expected_http_status_code=400) def test_import_with_invalid_parameters(self): with self.subTest('scan_date in the future'): @@ -379,7 +379,7 @@ def test_reimport_by_product_name_exists_engagement_name_exists_no_title(self): def test_reimport_by_product_name_exists_engagement_name_exists_scan_type_not_exsists_test_title_exists(self): with assertImportModelsCreated(self, tests=0, engagements=0, products=0, product_types=0, endpoints=0): - import0 = self.reimport_scan_with_params(None, NPM_AUDIT_NO_VULN_FILENAME, scan_type='Acunetix Scan', product_name=PRODUCT_NAME_DEFAULT, + self.reimport_scan_with_params(None, NPM_AUDIT_NO_VULN_FILENAME, scan_type='Acunetix Scan', product_name=PRODUCT_NAME_DEFAULT, engagement=None, engagement_name=ENGAGEMENT_NAME_DEFAULT, test_title=TEST_TITLE_DEFAULT, expected_http_status_code=400) @patch('dojo.jira_link.helper.get_jira_project') @@ -394,7 +394,7 @@ def test_reimport_by_product_name_exists_engagement_name_exists_scan_type_not_ex def test_reimport_by_product_name_exists_engagement_name_exists_scan_type_not_exsists_test_title_not_exists(self): with assertImportModelsCreated(self, tests=0, engagements=0, products=0, product_types=0, endpoints=0): - import0 = self.reimport_scan_with_params(None, NPM_AUDIT_NO_VULN_FILENAME, scan_type='Acunetix Scan', product_name=PRODUCT_NAME_DEFAULT, + self.reimport_scan_with_params(None, NPM_AUDIT_NO_VULN_FILENAME, scan_type='Acunetix Scan', product_name=PRODUCT_NAME_DEFAULT, engagement=None, engagement_name=ENGAGEMENT_NAME_DEFAULT, test_title='bogus title', expected_http_status_code=400) @patch('dojo.jira_link.helper.get_jira_project') @@ -419,7 +419,7 @@ def test_reimport_by_product_name_exists_engagement_name_exists_test_title_exist def test_reimport_by_product_name_exists_engagement_name_not_exists(self): with assertImportModelsCreated(self, tests=0, engagements=0, products=0, product_types=0, endpoints=0): - import0 = self.reimport_scan_with_params(None, NPM_AUDIT_NO_VULN_FILENAME, scan_type=NPM_AUDIT_SCAN_TYPE, product_name=PRODUCT_NAME_DEFAULT, + self.reimport_scan_with_params(None, NPM_AUDIT_NO_VULN_FILENAME, scan_type=NPM_AUDIT_SCAN_TYPE, product_name=PRODUCT_NAME_DEFAULT, engagement=None, engagement_name=ENGAGEMENT_NAME_NEW, expected_http_status_code=400) @patch('dojo.jira_link.helper.get_jira_project') @@ -438,7 +438,7 @@ def test_reimport_by_product_name_exists_engagement_name_not_exists_auto_create( def test_reimport_by_product_name_not_exists_engagement_name(self): with assertImportModelsCreated(self, tests=0, engagements=0, products=0, product_types=0, endpoints=0): - import0 = self.reimport_scan_with_params(None, NPM_AUDIT_NO_VULN_FILENAME, scan_type=NPM_AUDIT_SCAN_TYPE, product_name=PRODUCT_NAME_NEW, + self.reimport_scan_with_params(None, NPM_AUDIT_NO_VULN_FILENAME, scan_type=NPM_AUDIT_SCAN_TYPE, product_name=PRODUCT_NAME_NEW, engagement=None, engagement_name=ENGAGEMENT_NAME_NEW, expected_http_status_code=400) @patch('dojo.jira_link.helper.get_jira_project') diff --git a/unittests/test_jira_config_engagement.py b/unittests/test_jira_config_engagement.py index 19ccaaa3aa1..f6922c19166 100644 --- a/unittests/test_jira_config_engagement.py +++ b/unittests/test_jira_config_engagement.py @@ -146,7 +146,7 @@ def add_engagement_jira(self, data, expect_redirect_to=None, expect_200=False): engagement = Engagement.objects.get(id=response.url.split('/')[-2]) except: raise ValueError('error parsing id from redirect uri: ' + response.url) - self.assertTrue(response.url == (expect_redirect_to % engagement.id)) + self.assertEqual(response.url, (expect_redirect_to % engagement.id)) else: self.assertEqual(response.status_code, 200) @@ -247,21 +247,21 @@ def test_add_jira_project_to_engagement_without_jira_project(self, jira_mock): jira_mock.return_value = True # cannot set return_value in decorated AND have the mock into the method # TODO: add engagement also via API, but let's focus on JIRA here engagement = self.add_engagement_without_jira_project(expected_delta_jira_project_db=0) - response = self.edit_jira_project_for_engagement(engagement, expected_delta_jira_project_db=1) + self.edit_jira_project_for_engagement(engagement, expected_delta_jira_project_db=1) self.assertEqual(jira_mock.call_count, 1) @patch('dojo.jira_link.views.jira_helper.is_jira_project_valid') def test_add_empty_jira_project_to_engagement_without_jira_project(self, jira_mock): jira_mock.return_value = True # cannot set return_value in decorated AND have the mock into the method engagement = self.add_engagement_without_jira_project(expected_delta_jira_project_db=0) - response = self.empty_jira_project_for_engagement(engagement, expected_delta_jira_project_db=0) + self.empty_jira_project_for_engagement(engagement, expected_delta_jira_project_db=0) self.assertEqual(jira_mock.call_count, 0) @patch('dojo.jira_link.views.jira_helper.is_jira_project_valid') def test_edit_jira_project_to_engagement_with_jira_project(self, jira_mock): jira_mock.return_value = True # cannot set return_value in decorated AND have the mock into the method engagement = self.add_engagement_with_jira_project(expected_delta_jira_project_db=1) - response = self.edit_jira_project_for_engagement2(engagement, expected_delta_jira_project_db=0) + self.edit_jira_project_for_engagement2(engagement, expected_delta_jira_project_db=0) self.assertEqual(jira_mock.call_count, 2) @patch('dojo.jira_link.views.jira_helper.is_jira_project_valid') @@ -274,14 +274,14 @@ def test_edit_empty_jira_project_to_engagement_with_jira_project(self, jira_mock # - so prevent clearing out these values # response = self.empty_jira_project_for_engagement(Engagement.objects.get(id=3), -1) # expecting ValueError as we can't delete existing JIRA Projects - response = self.empty_jira_project_for_engagement(engagement, expected_delta_jira_project_db=0, expect_error=True) + self.empty_jira_project_for_engagement(engagement, expected_delta_jira_project_db=0, expect_error=True) self.assertEqual(jira_mock.call_count, 1) @patch('dojo.jira_link.views.jira_helper.is_jira_project_valid') def test_add_jira_project_to_engagement_without_jira_project_invalid_project(self, jira_mock): jira_mock.return_value = False # cannot set return_value in decorated AND have the mock into the method # errors means it won't redirect to view_engagement, but returns a 200 and redisplays the edit engagement page - response = self.edit_jira_project_for_engagement(Engagement.objects.get(id=3), expected_delta_jira_project_db=0, expect_200=True) + self.edit_jira_project_for_engagement(Engagement.objects.get(id=3), expected_delta_jira_project_db=0, expect_200=True) self.assertEqual(jira_mock.call_count, 1) @patch('dojo.jira_link.views.jira_helper.is_jira_project_valid') @@ -290,7 +290,7 @@ def test_edit_jira_project_to_engagement_with_jira_project_invalid_project(self, engagement = self.add_engagement_with_jira_project(expected_delta_jira_project_db=1) jira_mock.return_value = False # jira key is changed, so jira project will be checked - response = self.edit_jira_project_for_engagement2(engagement, expected_delta_jira_project_db=0, expect_200=True) + self.edit_jira_project_for_engagement2(engagement, expected_delta_jira_project_db=0, expect_200=True) self.assertEqual(jira_mock.call_count, 2) @patch('dojo.jira_link.views.jira_helper.is_jira_project_valid') @@ -329,8 +329,8 @@ def test_add_engagement_with_jira_project_to_engagement_jira_disabled(self, jira def test_edit_jira_project_to_engagement_with_jira_project_invalid_project_jira_disabled(self, jira_mock): self.system_settings(enable_jira=False) jira_mock.return_value = True # cannot set return_value in decorated AND have the mock into the method - response = self.edit_jira_project_for_engagement(Engagement.objects.get(id=3), expected_delta_jira_project_db=0) - response = self.edit_jira_project_for_engagement2(Engagement.objects.get(id=3), expected_delta_jira_project_db=0) + self.edit_jira_project_for_engagement(Engagement.objects.get(id=3), expected_delta_jira_project_db=0) + self.edit_jira_project_for_engagement2(Engagement.objects.get(id=3), expected_delta_jira_project_db=0) self.assertEqual(jira_mock.call_count, 0) diff --git a/unittests/test_jira_config_product.py b/unittests/test_jira_config_product.py index ab6d378ae36..150709574d1 100644 --- a/unittests/test_jira_config_product.py +++ b/unittests/test_jira_config_product.py @@ -86,7 +86,7 @@ def test_add_jira_instance_unknown_host(self): # test raw connection error with self.assertRaises(requests.exceptions.RequestException): - jira = jira_helper.get_jira_connection_raw(data['url'], data['username'], data['password']) + jira_helper.get_jira_connection_raw(data['url'], data['username'], data['password']) @patch('dojo.jira_link.views.jira_helper.get_jira_connection_raw') def test_add_jira_instance_invalid_credentials(self, jira_mock): @@ -102,29 +102,29 @@ def test_add_jira_instance_invalid_credentials(self, jira_mock): self.assertEqual(200, response.status_code) content = response.content.decode('utf-8') - self.assertTrue('Login failed' in content) - self.assertTrue('Unable to authenticate to JIRA' in content) + self.assertIn('Login failed', content) + self.assertIn('Unable to authenticate to JIRA', content) @patch('dojo.jira_link.views.jira_helper.is_jira_project_valid') def test_add_jira_project_to_product_without_jira_project(self, jira_mock): jira_mock.return_value = True # cannot set return_value in decorated AND have the mock into the method # TODO: add product also via API, but let's focus on JIRA here product = self.add_product_without_jira_project(expected_delta_jira_project_db=0) - response = self.edit_jira_project_for_product(product, expected_delta_jira_project_db=1) + self.edit_jira_project_for_product(product, expected_delta_jira_project_db=1) self.assertEqual(jira_mock.call_count, 1) @patch('dojo.jira_link.views.jira_helper.is_jira_project_valid') def test_add_empty_jira_project_to_product_without_jira_project(self, jira_mock): jira_mock.return_value = True # cannot set return_value in decorater AND have the mock into the method product = self.add_product_without_jira_project(expected_delta_jira_project_db=0) - response = self.empty_jira_project_for_product(product, expected_delta_jira_project_db=0) + self.empty_jira_project_for_product(product, expected_delta_jira_project_db=0) self.assertEqual(jira_mock.call_count, 0) @patch('dojo.jira_link.views.jira_helper.is_jira_project_valid') def test_edit_jira_project_to_product_with_jira_project(self, jira_mock): jira_mock.return_value = True # cannot set return_value in decorated AND have the mock into the method product = self.add_product_with_jira_project(expected_delta_jira_project_db=1) - response = self.edit_jira_project_for_product2(product, expected_delta_jira_project_db=0) + self.edit_jira_project_for_product2(product, expected_delta_jira_project_db=0) self.assertEqual(jira_mock.call_count, 2) @patch('dojo.jira_link.views.jira_helper.is_jira_project_valid') @@ -137,14 +137,14 @@ def test_edit_empty_jira_project_to_product_with_jira_project(self, jira_mock): # - so prevent clearing out these values # response = self.empty_jira_project_for_product(Product.objects.get(id=3), -1) # errors means it won't redirect to view_product, but returns a 200 and redisplays the edit product page - response = self.empty_jira_project_for_product(product, expected_delta_jira_project_db=0, expect_200=True) + self.empty_jira_project_for_product(product, expected_delta_jira_project_db=0, expect_200=True) self.assertEqual(jira_mock.call_count, 1) @patch('dojo.jira_link.views.jira_helper.is_jira_project_valid') def test_add_jira_project_to_product_without_jira_project_invalid_project(self, jira_mock): jira_mock.return_value = False # cannot set return_value in decorated AND have the mock into the method # errors means it won't redirect to view_product, but returns a 200 and redisplays the edit product page - response = self.edit_jira_project_for_product(Product.objects.get(id=3), expected_delta_jira_project_db=0, expect_200=True) + self.edit_jira_project_for_product(Product.objects.get(id=3), expected_delta_jira_project_db=0, expect_200=True) self.assertEqual(jira_mock.call_count, 1) @patch('dojo.jira_link.views.jira_helper.is_jira_project_valid') @@ -153,7 +153,7 @@ def test_edit_jira_project_to_product_with_jira_project_invalid_project(self, ji product = self.add_product_with_jira_project(expected_delta_jira_project_db=1) jira_mock.return_value = False # jira key is changed, so jira project will be checked - response = self.edit_jira_project_for_product2(product, expected_delta_jira_project_db=0, expect_200=True) + self.edit_jira_project_for_product2(product, expected_delta_jira_project_db=0, expect_200=True) self.assertEqual(jira_mock.call_count, 2) @patch('dojo.jira_link.views.jira_helper.is_jira_project_valid') @@ -192,8 +192,8 @@ def test_add_product_with_jira_project_to_product_jira_disabled(self, jira_mock) def test_edit_jira_project_to_product_with_jira_project_invalid_project_jira_disabled(self, jira_mock): self.system_settings(enable_jira=False) jira_mock.return_value = True # cannot set return_value in decorated AND have the mock into the method - response = self.edit_jira_project_for_product(Product.objects.get(id=3), expected_delta_jira_project_db=0) - response = self.edit_jira_project_for_product2(Product.objects.get(id=3), expected_delta_jira_project_db=0) + self.edit_jira_project_for_product(Product.objects.get(id=3), expected_delta_jira_project_db=0) + self.edit_jira_project_for_product2(Product.objects.get(id=3), expected_delta_jira_project_db=0) self.assertEqual(jira_mock.call_count, 0) diff --git a/unittests/test_jira_import_and_pushing_api.py b/unittests/test_jira_import_and_pushing_api.py index 801f697e208..dd50794c0a2 100644 --- a/unittests/test_jira_import_and_pushing_api.py +++ b/unittests/test_jira_import_and_pushing_api.py @@ -152,7 +152,7 @@ def test_import_no_push_to_jira_reimport_no_push_to_jira(self): self.assert_jira_issue_count_in_test(test_id, 0) self.assert_jira_group_issue_count_in_test(test_id, 0) - reimport = self.reimport_scan_with_params(test_id, self.zap_sample5_filename, verified=True) + self.reimport_scan_with_params(test_id, self.zap_sample5_filename, verified=True) self.assert_jira_issue_count_in_test(test_id, 0) self.assert_jira_group_issue_count_in_test(test_id, 0) @@ -162,7 +162,7 @@ def test_import_no_push_to_jira_reimport_push_to_jira_false(self): self.assert_jira_issue_count_in_test(test_id, 0) self.assert_jira_group_issue_count_in_test(test_id, 0) - reimport = self.reimport_scan_with_params(test_id, self.zap_sample5_filename, push_to_jira=False, verified=True) + self.reimport_scan_with_params(test_id, self.zap_sample5_filename, push_to_jira=False, verified=True) self.assert_jira_issue_count_in_test(test_id, 0) self.assert_jira_group_issue_count_in_test(test_id, 0) @@ -172,7 +172,7 @@ def test_import_no_push_to_jira_reimport_with_push_to_jira(self): self.assert_jira_issue_count_in_test(test_id, 0) self.assert_jira_group_issue_count_in_test(test_id, 0) - reimport = self.reimport_scan_with_params(test_id, self.zap_sample5_filename, push_to_jira=True, verified=True) + self.reimport_scan_with_params(test_id, self.zap_sample5_filename, push_to_jira=True, verified=True) self.assert_jira_issue_count_in_test(test_id, 2) self.assert_jira_group_issue_count_in_test(test_id, 0) # by asserting full cassette is played we know issues have been updated in JIRA @@ -184,7 +184,7 @@ def test_import_with_groups_no_push_to_jira_reimport_with_push_to_jira(self): self.assert_jira_issue_count_in_test(test_id, 0) self.assert_jira_group_issue_count_in_test(test_id, 0) - reimport = self.reimport_scan_with_params(test_id, self.npm_groups_sample_filename, scan_type='NPM Audit Scan', group_by='component_name+component_version', push_to_jira=True, verified=True) + self.reimport_scan_with_params(test_id, self.npm_groups_sample_filename, scan_type='NPM Audit Scan', group_by='component_name+component_version', push_to_jira=True, verified=True) self.assert_jira_issue_count_in_test(test_id, 0) self.assert_jira_group_issue_count_in_test(test_id, 3) # by asserting full cassette is played we know issues have been updated in JIRA @@ -197,7 +197,7 @@ def test_import_no_push_to_jira_reimport_no_push_to_jira_but_push_all_issues(sel self.assert_jira_issue_count_in_test(test_id, 2) self.assert_jira_group_issue_count_in_test(test_id, 0) - reimport = self.reimport_scan_with_params(test_id, self.zap_sample5_filename, verified=True) + self.reimport_scan_with_params(test_id, self.zap_sample5_filename, verified=True) self.assert_jira_issue_count_in_test(test_id, 2) self.assert_jira_group_issue_count_in_test(test_id, 0) # by asserting full cassette is played we know issues have been updated in JIRA @@ -210,7 +210,7 @@ def test_import_with_groups_no_push_to_jira_reimport_no_push_to_jira_but_push_al self.assert_jira_issue_count_in_test(test_id, 0) self.assert_jira_group_issue_count_in_test(test_id, 3) - reimport = self.reimport_scan_with_params(test_id, self.npm_groups_sample_filename, scan_type='NPM Audit Scan', group_by='component_name+component_version', verified=True) + self.reimport_scan_with_params(test_id, self.npm_groups_sample_filename, scan_type='NPM Audit Scan', group_by='component_name+component_version', verified=True) self.assert_jira_issue_count_in_test(test_id, 0) self.assert_jira_group_issue_count_in_test(test_id, 3) # by asserting full cassette is played we know issues have been updated in JIRA @@ -222,9 +222,9 @@ def test_import_no_push_to_jira_reimport_push_to_jira_is_false_but_push_all_issu test_id = import0['test'] self.assert_jira_issue_count_in_test(test_id, 2) self.assert_jira_group_issue_count_in_test(test_id, 0) - updated_map = self.get_jira_issue_updated_map(test_id) + self.get_jira_issue_updated_map(test_id) - reimport = self.reimport_scan_with_params(test_id, self.zap_sample5_filename, push_to_jira=False, verified=True) + self.reimport_scan_with_params(test_id, self.zap_sample5_filename, push_to_jira=False, verified=True) self.assert_jira_issue_count_in_test(test_id, 2) self.assert_jira_group_issue_count_in_test(test_id, 0) # when sending in identical data to JIRA, JIRA does NOT update the updated timestamp.... @@ -240,7 +240,7 @@ def test_import_with_groups_no_push_to_jira_reimport_push_to_jira_is_false_but_p self.assert_jira_group_issue_count_in_test(test_id, 3) updated_map = self.get_jira_issue_updated_map(test_id) - reimport = self.reimport_scan_with_params(test_id, self.npm_groups_sample_filename, scan_type='NPM Audit Scan', group_by='component_name+component_version', push_to_jira=False, verified=True) + self.reimport_scan_with_params(test_id, self.npm_groups_sample_filename, scan_type='NPM Audit Scan', group_by='component_name+component_version', push_to_jira=False, verified=True) self.assert_jira_issue_count_in_test(test_id, 0) self.assert_jira_group_issue_count_in_test(test_id, 3) # when sending in identical data to JIRA, JIRA does NOT update the updated timestamp.... @@ -256,12 +256,12 @@ def test_import_push_to_jira_reimport_with_push_to_jira(self): self.assert_jira_group_issue_count_in_test(test_id, 0) # Get one of the findings from the test finding_id = Finding.objects.filter(test__id=test_id).first().id - pre_jira_status = self.get_jira_issue_updated(finding_id) + self.get_jira_issue_updated(finding_id) # re-import and see status change - reimport = self.reimport_scan_with_params(test_id, self.zap_sample5_filename, push_to_jira=True, verified=True) + self.reimport_scan_with_params(test_id, self.zap_sample5_filename, push_to_jira=True, verified=True) self.assert_jira_issue_count_in_test(test_id, 2) self.assert_jira_group_issue_count_in_test(test_id, 0) - post_jira_status = self.get_jira_issue_updated(finding_id) + self.get_jira_issue_updated(finding_id) # when sending in identical data to JIRA, JIRA does NOT update the updated timestamp.... # self.assert_jira_updated_change(pre_jira_status, post_jira_status) # by asserting full cassette is played we know issues have been updated in JIRA @@ -468,7 +468,7 @@ def test_import_with_push_to_jira_add_comment(self): finding_id = findings['results'][0]['id'] - response = self.post_finding_notes_api(finding_id, 'testing note. creating it and pushing it to JIRA') + self.post_finding_notes_api(finding_id, 'testing note. creating it and pushing it to JIRA') self.patch_finding_api(finding_id, {"push_to_jira": True}) # Make sure the number of comments match self.assertEqual(len(self.get_jira_comments(finding_id)), 1) @@ -483,8 +483,8 @@ def test_import_add_comments_then_push_to_jira(self): finding_id = findings['results'][0]['id'] - response = self.post_finding_notes_api(finding_id, 'testing note. creating it and pushing it to JIRA') - response = self.post_finding_notes_api(finding_id, 'testing second note. creating it and pushing it to JIRA') + self.post_finding_notes_api(finding_id, 'testing note. creating it and pushing it to JIRA') + self.post_finding_notes_api(finding_id, 'testing second note. creating it and pushing it to JIRA') self.patch_finding_api(finding_id, {"push_to_jira": True}) self.assert_jira_issue_count_in_test(test_id, 1) @@ -505,7 +505,7 @@ def test_import_with_push_to_jira_add_tags(self): finding = Finding.objects.get(id=findings['results'][0]['id']) tags = ['tag1', 'tag2'] - response = self.post_finding_tags_api(finding.id, tags) + self.post_finding_tags_api(finding.id, tags) self.patch_finding_api(finding.id, {"push_to_jira": True}) # Connect to jira to get the new issue @@ -530,7 +530,7 @@ def test_import_with_push_to_jira_update_tags(self): finding = Finding.objects.get(id=findings['results'][0]['id']) tags = ['tag1', 'tag2'] - response = self.post_finding_tags_api(finding.id, tags) + self.post_finding_tags_api(finding.id, tags) self.patch_finding_api(finding.id, {"push_to_jira": True}) # Connect to jira to get the new issue @@ -542,7 +542,7 @@ def test_import_with_push_to_jira_update_tags(self): self.assertEqual(issue.fields.labels, tags) tags_new = tags + ['tag3', 'tag4'] - response = self.post_finding_tags_api(finding.id, tags_new) + self.post_finding_tags_api(finding.id, tags_new) self.patch_finding_api(finding.id, {"push_to_jira": True}) # Connect to jira to get the new issue diff --git a/unittests/test_rest_framework.py b/unittests/test_rest_framework.py index c5e10179cb0..01d6ed27492 100644 --- a/unittests/test_rest_framework.py +++ b/unittests/test_rest_framework.py @@ -347,7 +347,7 @@ def test_list(self): self.assertEqual(len(check_for_tags), len(result.get('tags', None))) for tag in check_for_tags: # logger.debug('looking for tag %s in tag list %s', tag, result['tags']) - self.assertTrue(tag in result['tags']) + self.assertIn(tag, result['tags']) tags_found = True self.assertTrue(tags_found) @@ -369,7 +369,7 @@ def test_create(self): self.assertEqual(len(self.payload.get('tags')), len(response.data.get('tags', None))) for tag in self.payload.get('tags'): # logger.debug('looking for tag %s in tag list %s', tag, response.data['tags']) - self.assertTrue(tag in response.data['tags']) + self.assertIn(tag, response.data['tags']) self.check_schema_response('post', '201', response) @@ -381,9 +381,9 @@ def test_detail(self): self.assertEqual(200, response.status_code, response.content[:1000]) # sensitive data must be set to write_only so those are not returned in the response # https://github.com/DefectDojo/django-DefectDojo/security/advisories/GHSA-8q8j-7wc4-vjg5 - self.assertFalse('password' in response.data) - self.assertFalse('ssh' in response.data) - self.assertFalse('api_key' in response.data) + self.assertNotIn('password', response.data) + self.assertNotIn('ssh', response.data) + self.assertNotIn('api_key', response.data) self.check_schema_response('get', '200', response, detail=True) @@ -418,16 +418,16 @@ def test_update(self): response_data = response.data[key] self.assertEqual(value, response_data) - self.assertFalse('push_to_jira' in response.data) - self.assertFalse('ssh' in response.data) - self.assertFalse('password' in response.data) - self.assertFalse('api_key' in response.data) + self.assertNotIn('push_to_jira', response.data) + self.assertNotIn('ssh', response.data) + self.assertNotIn('password', response.data) + self.assertNotIn('api_key', response.data) if hasattr(self.endpoint_model, 'tags') and self.update_fields and self.update_fields.get('tags', None): self.assertEqual(len(self.update_fields.get('tags')), len(response.data.get('tags', None))) for tag in self.update_fields.get('tags'): logger.debug('looking for tag %s in tag list %s', tag, response.data['tags']) - self.assertTrue(tag in response.data['tags']) + self.assertIn(tag, response.data['tags']) response = self.client.put( relative_url, self.payload) @@ -448,17 +448,17 @@ def test_delete_preview(self): self.check_schema_response('get', '200', response, detail=True) - self.assertFalse('push_to_jira' in response.data) - self.assertFalse('password' in response.data) - self.assertFalse('ssh' in response.data) - self.assertFalse('api_key' in response.data) + self.assertNotIn('push_to_jira', response.data) + self.assertNotIn('password', response.data) + self.assertNotIn('ssh', response.data) + self.assertNotIn('api_key', response.data) self.assertIsInstance(response.data['results'], list) - self.assertTrue(len(response.data['results']) > 0, "Length: {}".format(len(response.data['results']))) + self.assertGreater(len(response.data['results']), 0, "Length: {}".format(len(response.data['results']))) for obj in response.data['results']: self.assertIsInstance(obj, dict) - self.assertTrue(len(obj), 3) + self.assertEqual(len(obj), 3) self.assertIsInstance(obj['model'], str) if obj['id']: # It needs to be None or int self.assertIsInstance(obj['id'], int) @@ -479,18 +479,18 @@ def test_detail_prefetch(self): self.assertEqual(200, response.status_code) obj = response.data - self.assertTrue("prefetch" in obj) + self.assertIn("prefetch", obj) for field in prefetchable_fields: field_value = obj.get(field, None) if field_value is None: continue - self.assertTrue(field in obj["prefetch"]) - values = field_value if type(field_value) is list else [field_value] + self.assertIn(field, obj["prefetch"]) + values = field_value if isinstance(field_value, list) else [field_value] for value in values: - self.assertTrue(value in obj["prefetch"][field]) + self.assertIn(value, obj["prefetch"][field]) # TODO add schema check @@ -504,8 +504,8 @@ def test_list_prefetch(self): self.assertEqual(200, response.status_code) objs = response.data - self.assertTrue("results" in objs) - self.assertTrue("prefetch" in objs) + self.assertIn("results", objs) + self.assertIn("prefetch", objs) for obj in objs["results"]: for field in prefetchable_fields: @@ -513,13 +513,13 @@ def test_list_prefetch(self): if field_value is None: continue - self.assertTrue(field in objs["prefetch"]) - values = field_value if type(field_value) is list else [field_value] + self.assertIn(field, objs["prefetch"]) + values = field_value if isinstance(field_value, list) else [field_value] for value in values: - if type(value) is not int: + if not isinstance(value, int): value = value['id'] - self.assertTrue(value in objs["prefetch"][field]) + self.assertIn(value, objs["prefetch"][field]) # TODO add schema check @@ -588,7 +588,7 @@ def test_delete_object_not_authorized(self, mock): current_objects = self.client.get(self.url, format='json').data relative_url = self.url + '%s/' % current_objects['results'][0]['id'] - response = self.client.delete(relative_url) + self.client.delete(relative_url) if self.endpoint_model == Endpoint_Status: permission_object = Endpoint.objects.get(id=current_objects['results'][0]['endpoint']) @@ -967,6 +967,12 @@ def __init__(self, *args, **kwargs): self.deleted_objects = 3 BaseClass.RESTEndpointTest.__init__(self, *args, **kwargs) + def test_create_object_not_authorized(self): + self.setUp_not_authorized() + + response = self.client.post(self.url, self.payload) + self.assertEqual(403, response.status_code, response.content[:1000]) + class FindingRequestResponseTest(DojoAPITestCase): fixtures = ['dojo_testdata.json'] diff --git a/unittests/test_risk_acceptance.py b/unittests/test_risk_acceptance.py index 4de529721a3..e652fc132b7 100644 --- a/unittests/test_risk_acceptance.py +++ b/unittests/test_risk_acceptance.py @@ -128,7 +128,7 @@ def test_remove_risk_acceptance_findings_active(self): data = {'id': ra.id} - response = self.client.post(reverse('delete_risk_acceptance', args=(1, ra.id, )), data) + self.client.post(reverse('delete_risk_acceptance', args=(1, ra.id, )), data) self.assert_all_active_not_risk_accepted(findings) self.assert_all_active_not_risk_accepted(Finding.objects.filter(test__engagement=1)) @@ -143,7 +143,7 @@ def test_expire_risk_acceptance_findings_active(self): data = {'id': ra.id} - response = self.client.post(reverse('expire_risk_acceptance', args=(1, ra.id, )), data) + self.client.post(reverse('expire_risk_acceptance', args=(1, ra.id, )), data) ra.refresh_from_db() self.assert_all_active_not_risk_accepted(findings) @@ -165,7 +165,7 @@ def test_expire_risk_acceptance_findings_not_active(self): data = {'id': ra.id} - response = self.client.post(reverse('expire_risk_acceptance', args=(1, ra.id, )), data) + self.client.post(reverse('expire_risk_acceptance', args=(1, ra.id, )), data) ra.refresh_from_db() # no reactivation on expiry @@ -188,7 +188,7 @@ def test_expire_risk_acceptance_sla_not_reset(self): data = {'id': ra.id} - response = self.client.post(reverse('expire_risk_acceptance', args=(1, ra.id, )), data) + self.client.post(reverse('expire_risk_acceptance', args=(1, ra.id, )), data) ra.refresh_from_db() @@ -204,7 +204,7 @@ def test_expire_risk_acceptance_sla_reset(self): data = {'id': ra.id} - response = self.client.post(reverse('expire_risk_acceptance', args=(1, ra.id, )), data) + self.client.post(reverse('expire_risk_acceptance', args=(1, ra.id, )), data) ra.refresh_from_db() @@ -219,7 +219,7 @@ def test_reinstate_risk_acceptance_findings_accepted(self): data = {'id': ra.id} - response = self.client.post(reverse('reinstate_risk_acceptance', args=(1, ra.id, )), data) + self.client.post(reverse('reinstate_risk_acceptance', args=(1, ra.id, )), data) ra.refresh_from_db() expiration_delta_days = get_system_setting('risk_acceptance_form_default_days', 90) @@ -237,19 +237,19 @@ def create_multiple_ras(self): ra_data = copy.copy(self.data_risk_accceptance) ra_data['accepted_findings'] = [2] ra_data['return_url'] = reverse('view_finding', args=(2, )) - response = self.add_risk_acceptance(1, ra_data, 2) + self.add_risk_acceptance(1, ra_data, 2) ra1 = Risk_Acceptance.objects.last() ra_data = copy.copy(self.data_risk_accceptance) ra_data['accepted_findings'] = [7] ra_data['return_url'] = reverse('view_finding', args=(7, )) - response = self.add_risk_acceptance(1, ra_data, 7) + self.add_risk_acceptance(1, ra_data, 7) ra2 = Risk_Acceptance.objects.last() ra_data = copy.copy(self.data_risk_accceptance) ra_data['accepted_findings'] = [22] ra_data['return_url'] = reverse('view_finding', args=(22, )) - response = self.add_risk_acceptance(3, ra_data, 22) + self.add_risk_acceptance(3, ra_data, 22) ra3 = Risk_Acceptance.objects.last() return ra1, ra2, ra3 @@ -274,13 +274,13 @@ def test_expiration_handler(self): to_warn = ra_helper.get_almost_expired_risk_acceptances_to_handle(heads_up_days=heads_up_days) to_expire = ra_helper.get_expired_risk_acceptances_to_handle() - self.assertTrue(ra1 in to_warn) - self.assertFalse(ra2 in to_warn) - self.assertFalse(ra3 in to_warn) + self.assertIn(ra1, to_warn) + self.assertNotIn(ra2, to_warn) + self.assertNotIn(ra3, to_warn) - self.assertFalse(ra1 in to_expire) - self.assertFalse(ra2 in to_expire) - self.assertTrue(ra3 in to_expire) + self.assertNotIn(ra1, to_expire) + self.assertNotIn(ra2, to_expire) + self.assertIn(ra3, to_expire) # run job ra_helper.expiration_handler() diff --git a/unittests/test_tags.py b/unittests/test_tags.py index ccb8eb69dae..11259d582d0 100644 --- a/unittests/test_tags.py +++ b/unittests/test_tags.py @@ -36,14 +36,14 @@ def test_finding_get_tags(self): self.assertEqual(len(tags), len(response.get('tags', None))) for tag in tags: # logger.debug('looking for tag %s in tag list %s', tag, response['tags']) - self.assertTrue(tag in response['tags']) + self.assertIn(tag, response['tags']) def test_finding_filter_tags(self): tags = ['tag1', 'tag2'] - finding_id = self.create_finding_with_tags(tags) + self.create_finding_with_tags(tags) tags2 = ['tag1', 'tag3'] - finding_id2 = self.create_finding_with_tags(tags2) + self.create_finding_with_tags(tags2) response = self.get_finding_api_filter_tags('tag1') self.assertEqual(response['count'], 2) @@ -69,7 +69,7 @@ def test_finding_post_tags(self): self.assertEqual(len(tags_merged), len(response.get('tags'))) for tag in tags_merged: # logger.debug('looking for tag %s in tag list %s', tag, response['tags']) - self.assertTrue(tag in response['tags']) + self.assertIn(tag, response['tags']) def test_finding_post_tags_overlap(self): # create finding @@ -83,7 +83,7 @@ def test_finding_post_tags_overlap(self): self.assertEqual(len(tags_merged), len(response.get('tags'))) for tag in tags_merged: # logger.debug('looking for tag %s in tag list %s', tag, response['tags']) - self.assertTrue(tag in response['tags']) + self.assertIn(tag, response['tags']) def test_finding_put_remove_tags(self): # create finding @@ -103,7 +103,7 @@ def test_finding_put_remove_tags(self): self.assertEqual(len(tags_merged), len(response.get('tags'))) for tag in tags_merged: # logger.debug('looking for tag %s in tag list %s', tag, response['tags']) - self.assertTrue(tag in response['tags']) + self.assertIn(tag, response['tags']) def test_finding_put_remove_tags_all(self): # create finding @@ -123,7 +123,7 @@ def test_finding_put_remove_tags_all(self): self.assertEqual(len(tags_merged), len(response.get('tags'))) for tag in tags_merged: # logger.debug('looking for tag %s in tag list %s', tag, response['tags']) - self.assertTrue(tag in response['tags']) + self.assertIn(tag, response['tags']) def test_finding_put_remove_tags_non_existent(self): # create finding @@ -143,7 +143,7 @@ def test_finding_put_remove_tags_non_existent(self): self.assertEqual(len(tags_merged), len(response.get('tags'))) for tag in tags_merged: # logger.debug('looking for tag %s in tag list %s', tag, response['tags']) - self.assertTrue(tag in response['tags']) + self.assertIn(tag, response['tags']) def test_finding_patch_remove_tags(self): # has same logic as PUT @@ -168,8 +168,8 @@ def test_finding_create_tags_with_commas(self): # self.assertEqual(2, len(response.get('tags'))) self.assertEqual(1, len(response.get('tags'))) # print("response['tags']:" + str(response['tags'])) - self.assertTrue('one' in str(response['tags'])) - self.assertTrue('two' in str(response['tags'])) + self.assertIn('one', str(response['tags'])) + self.assertIn('two', str(response['tags'])) def test_finding_create_tags_with_commas_quoted(self): tags = ['"one,two"'] @@ -181,8 +181,8 @@ def test_finding_create_tags_with_commas_quoted(self): for tag in tags: logger.debug('looking for tag %s in tag list %s', tag, response['tags']) # with django-tagging the quotes were stripped, with tagulous they remain - # self.assertTrue(tag.strip('\"') in response['tags']) - self.assertTrue(tag in response['tags']) + # self.assertIn(tag.strip('\"'), response['tags']) + self.assertIn(tag, response['tags']) def test_finding_create_tags_with_spaces(self): tags = ['one two'] @@ -195,8 +195,8 @@ def test_finding_create_tags_with_spaces(self): # tags with commas, so should be minor trouble # self.assertEqual(2, len(response.get('tags'))) self.assertEqual(1, len(response.get('tags'))) - self.assertTrue('one' in str(response['tags'])) - self.assertTrue('two' in str(response['tags'])) + self.assertIn('one', str(response['tags'])) + self.assertIn('two', str(response['tags'])) # finding.tags: [, ] def test_finding_create_tags_with_spaces_quoted(self): @@ -209,8 +209,8 @@ def test_finding_create_tags_with_spaces_quoted(self): for tag in tags: logger.debug('looking for tag %s in tag list %s', tag, response['tags']) # with django-tagging the quotes were stripped, with tagulous they remain - # self.assertTrue(tag.strip('\"') in response['tags']) - self.assertTrue(tag in response['tags']) + # self.assertIn(tag.strip('\"'), response['tags']) + self.assertIn(tag, response['tags']) # finding.tags: ]> @@ -222,7 +222,7 @@ def test_finding_create_tags_with_slashes(self): self.assertEqual(len(tags), len(response.get('tags', None))) for tag in tags: # logger.debug('looking for tag %s in tag list %s', tag, response['tags']) - self.assertTrue(tag in response['tags']) + self.assertIn(tag, response['tags']) def test_import_and_reimport_with_tags(self): tags = ['tag1', 'tag2'] @@ -233,19 +233,19 @@ def test_import_and_reimport_with_tags(self): self.assertEqual(len(tags), len(response.get('tags'))) for tag in tags: - self.assertTrue(tag in response['tags']) + self.assertIn(tag, response['tags']) # reimport, do not specify tags: should retain tags - reimport = self.reimport_scan_with_params(test_id, self.zap_sample5_filename) + self.reimport_scan_with_params(test_id, self.zap_sample5_filename) self.assertEqual(len(tags), len(response.get('tags'))) for tag in tags: - self.assertTrue(tag in response['tags']) + self.assertIn(tag, response['tags']) # reimport, specify tags others: currently reimport doesn't do anything with tags param and silently ignores them - reimport = self.reimport_scan_with_params(test_id, self.zap_sample5_filename, tags=['tag3', 'tag4']) + self.reimport_scan_with_params(test_id, self.zap_sample5_filename, tags=['tag3', 'tag4']) self.assertEqual(len(tags), len(response.get('tags'))) for tag in tags: - self.assertTrue(tag in response['tags']) + self.assertIn(tag, response['tags']) class InheritedTagsTests(DojoAPITestCase): diff --git a/unittests/tools/test_acunetix360_parser.py b/unittests/tools/test_acunetix360_parser.py index 890888505b0..d491a1de2b1 100644 --- a/unittests/tools/test_acunetix360_parser.py +++ b/unittests/tools/test_acunetix360_parser.py @@ -25,7 +25,7 @@ def test_parse_file_with_one_finding(self): endpoint = finding.unsaved_endpoints[0] self.assertEqual(str(endpoint), "http://php.testsparker.com/auth/login.php") self.assertEqual(finding.date, datetime(2021, 6, 16, 12, 30)) - self.assertTrue("https://online.acunetix360.com/issues/detail/735f4503-e9eb-4b4c-4306-ad49020a4c4b" in finding.references) + self.assertIn("https://online.acunetix360.com/issues/detail/735f4503-e9eb-4b4c-4306-ad49020a4c4b", finding.references) def test_parse_file_with_one_finding_false_positive(self): testfile = open("unittests/scans/acunetix360/acunetix360_one_finding_false_positive.json") diff --git a/unittests/tools/test_anchore_enterprise_parser.py b/unittests/tools/test_anchore_enterprise_parser.py index 0009d5be22d..81a35caa956 100644 --- a/unittests/tools/test_anchore_enterprise_parser.py +++ b/unittests/tools/test_anchore_enterprise_parser.py @@ -31,7 +31,7 @@ def test_anchore_policy_check_parser_invalid_format(self): with open(path.join(path.dirname(__file__), "../scans/anchore_enterprise/invalid_checks_format.json")) as testfile: with self.assertRaises(Exception): parser = AnchoreEnterpriseParser() - findings = parser.get_findings(testfile, Test()) + parser.get_findings(testfile, Test()) def test_anchore_policy_check_extract_vulnerability_id(self): vulnerability_id = extract_vulnerability_id("CVE-2019-14540+openapi-generator-cli-4.0.0.jar:jackson-databind") diff --git a/unittests/tools/test_anchore_grype_parser.py b/unittests/tools/test_anchore_grype_parser.py index bd0a31cce7f..d00a4835f5a 100644 --- a/unittests/tools/test_anchore_grype_parser.py +++ b/unittests/tools/test_anchore_grype_parser.py @@ -23,7 +23,7 @@ def test_parser_has_many_findings(self): for finding in findings: self.assertIn(finding.severity, Finding.SEVERITIES) vulnerability_ids = finding.unsaved_vulnerability_ids - self.assertTrue(len(vulnerability_ids) >= 1) + self.assertGreaterEqual(len(vulnerability_ids), 1) if finding.vuln_id_from_tool == "CVE-2011-3389": vulnerability_ids = finding.unsaved_vulnerability_ids self.assertEqual(1, len(vulnerability_ids)) @@ -46,7 +46,7 @@ def test_grype_parser_with_one_criticle_vuln_has_one_findings(self): for finding in findings: self.assertIn(finding.severity, Finding.SEVERITIES) vulnerability_ids = finding.unsaved_vulnerability_ids - self.assertTrue(len(vulnerability_ids) >= 1) + self.assertGreaterEqual(len(vulnerability_ids), 1) if finding.vuln_id_from_tool == "CVE-2019-9192": vulnerability_ids = finding.unsaved_vulnerability_ids self.assertEqual(1, len(vulnerability_ids)) @@ -68,7 +68,7 @@ def test_grype_parser_with_many_vulns3(self): for finding in findings: self.assertIn(finding.severity, Finding.SEVERITIES) vulnerability_ids = finding.unsaved_vulnerability_ids - self.assertTrue(len(vulnerability_ids) >= 1) + self.assertGreaterEqual(len(vulnerability_ids), 1) if finding.vuln_id_from_tool == "CVE-2011-3389": vulnerability_ids = finding.unsaved_vulnerability_ids self.assertEqual(1, len(vulnerability_ids)) @@ -90,13 +90,13 @@ def test_grype_parser_with_new_matcher_list(self): for finding in findings: self.assertIn(finding.severity, Finding.SEVERITIES) vulnerability_ids = finding.unsaved_vulnerability_ids - self.assertTrue(len(vulnerability_ids) >= 1) + self.assertGreaterEqual(len(vulnerability_ids), 1) if finding.vuln_id_from_tool == "CVE-1999-1338": vulnerability_ids = finding.unsaved_vulnerability_ids self.assertEqual(1, len(vulnerability_ids)) self.assertEqual('CVE-1999-1338', vulnerability_ids[0]) self.assertEqual("Medium", finding.severity) - self.assertTrue("javascript-matcher" in finding.description) + self.assertIn("javascript-matcher", finding.description) self.assertEqual("delegate", finding.component_name) self.assertEqual("3.2.0", finding.component_version) found = True diff --git a/unittests/tools/test_api_bugcrowd_parser.py b/unittests/tools/test_api_bugcrowd_parser.py index abdcfcd53e2..f692797a19e 100644 --- a/unittests/tools/test_api_bugcrowd_parser.py +++ b/unittests/tools/test_api_bugcrowd_parser.py @@ -42,9 +42,9 @@ def test_parse_file_with_one_vuln_has_one_findings(self): self.assertEqual( finding.unique_id_from_tool, "a4201d47-62e1-4287-9ff6-30807ae9d36a" ) - self.assertTrue( - "/submissions/a4201d47-62e1-4287-9ff6-30807ae9d36a" - in finding.references + self.assertIn( + "/submissions/a4201d47-62e1-4287-9ff6-30807ae9d36a", + finding.references ) for endpoint in finding.unsaved_endpoints: endpoint.clean() @@ -89,7 +89,7 @@ def test_parse_file_with_multiple_vuln_has_multiple_finding(self): endpoint.clean() self.assertEqual(finding_1.severity, "Info") self.assertEqual(finding_2.severity, "Critical") - self.assertEqual(finding_3.severity, "Medium") + self.assertEqual(finding_3.severity, "Info") self.assertEqual(finding_1.mitigation, "Do things properly1") self.assertEqual(finding_2.mitigation, "Do things properly2") @@ -102,7 +102,7 @@ def test_parse_file_with_multiple_vuln_has_multiple_finding(self): self.assertEqual(finding_1.is_mitigated, True) self.assertEqual(finding_2.is_mitigated, False) self.assertEqual(finding_3.is_mitigated, False) - self.assertEqual(finding_3.risk_accepted, True) + self.assertEqual(finding_3.risk_accepted, False) self.assertEqual( finding_1.unique_id_from_tool, "3b0e6b2a-c21e-493e-bd19-de40f525016e" @@ -140,6 +140,7 @@ def test_parse_file_with_not_reproducible_finding(self): # self.assertEqual(finding.description, description) self.assertEqual(finding.mitigation, "Properly do JWT") self.assertEqual(finding.active, False) + self.assertEqual(finding.false_p, True) self.assertEqual( finding.unique_id_from_tool, "a4201d47-62e1-4287-9ff6-30807ae9d36a" ) diff --git a/unittests/tools/test_auditjs_parser.py b/unittests/tools/test_auditjs_parser.py index 9685d450785..8d012aec61b 100644 --- a/unittests/tools/test_auditjs_parser.py +++ b/unittests/tools/test_auditjs_parser.py @@ -65,9 +65,9 @@ def test_auditjs_parser_empty_with_error(self): parser = AuditJSParser() parser.get_findings(testfile, Test()) testfile.close() - self.assertTrue( - "Invalid JSON format. Are you sure you used --json option ?" in str(context.exception) - ) + self.assertTrue( + "Invalid JSON format. Are you sure you used --json option ?" in str(context.exception) + ) def test_auditjs_parser_with_package_name_has_namespace(self): testfile = open("unittests/scans/auditjs/auditjs_with_package_namespace.json") diff --git a/unittests/tools/test_burp_api_parser.py b/unittests/tools/test_burp_api_parser.py index ae7850a57ca..7006de7b7f5 100644 --- a/unittests/tools/test_burp_api_parser.py +++ b/unittests/tools/test_burp_api_parser.py @@ -49,7 +49,6 @@ def test_convert_severity(self): self.assertEqual("Info", convert_severity({})) def test_convert_confidence(self): - confidence = None with self.subTest(confidence="certain"): self.assertGreater(3, convert_confidence({"confidence": "certain"})) with self.subTest(confidence="firm"): diff --git a/unittests/tools/test_burp_graphql_parser.py b/unittests/tools/test_burp_graphql_parser.py index 394469625d3..7c5dbb53072 100644 --- a/unittests/tools/test_burp_graphql_parser.py +++ b/unittests/tools/test_burp_graphql_parser.py @@ -60,7 +60,7 @@ def test_burp_null_title(self): with self.assertRaises(ValueError): parser = BurpGraphQLParser() - findings = parser.get_findings(test_file, Test()) + parser.get_findings(test_file, Test()) def test_burp_null_request_segments(self): with open(path.join(path.dirname(__file__), "../scans/burp_graphql/null_request_segments.json")) as test_file: diff --git a/unittests/tools/test_checkmarx_osa_parser.py b/unittests/tools/test_checkmarx_osa_parser.py index 4d25a4c9399..2b5b0ead33a 100644 --- a/unittests/tools/test_checkmarx_osa_parser.py +++ b/unittests/tools/test_checkmarx_osa_parser.py @@ -188,6 +188,6 @@ def test_checkmarx_osa_parse_file_with_no_libraryId_raises_ValueError( parser = CheckmarxOsaParser() parser.get_findings(my_file_handle, test) self.teardown(my_file_handle) - self.assertTrue( - "Invalid format: missing mandatory field libraryId:" in str(context.exception) - ) + self.assertEqual( + "Invalid format: missing mandatory field libraryId", str(context.exception) + ) diff --git a/unittests/tools/test_checkmarx_parser.py b/unittests/tools/test_checkmarx_parser.py index c3d01aa7ac9..c43e24fb572 100644 --- a/unittests/tools/test_checkmarx_parser.py +++ b/unittests/tools/test_checkmarx_parser.py @@ -330,7 +330,6 @@ def test_file_name_aggregated_parse_file_with_different_sourceFilename_same_sink self.teardown(my_file_handle) # aggregation is on sink filename so all vuln with different source filenames are aggregated self.assertEqual(1, len(findings)) - item = findings[0] # nb_occurences counts the number of aggregated vulnerabilities from tool self.assertEqual(2, findings[0].nb_occurences) mock.assert_called_with(product, 'Java', files=2) diff --git a/unittests/tools/test_checkov_parser.py b/unittests/tools/test_checkov_parser.py index f0457c66b2f..29585978e6d 100644 --- a/unittests/tools/test_checkov_parser.py +++ b/unittests/tools/test_checkov_parser.py @@ -27,7 +27,7 @@ def test_parse_file_with_multiple_vuln_has_multiple_findings(self): testfile = open("unittests/scans/checkov/checkov-report-many-vuln.json") parser = CheckovParser() findings = parser.get_findings(testfile, Test()) - self.assertTrue(len(findings) > 2) + self.assertGreater(len(findings), 2) def test_parse_file_with_multiple_check_type_has_multiple_check_type(self): testfile = open("unittests/scans/checkov/checkov-report-multiple-check_type.json") diff --git a/unittests/tools/test_codechecker_parser.py b/unittests/tools/test_codechecker_parser.py index b93a888382a..23094751aa9 100644 --- a/unittests/tools/test_codechecker_parser.py +++ b/unittests/tools/test_codechecker_parser.py @@ -37,14 +37,14 @@ def test_parse_file_with_multiple_vuln_has_multiple_findings(self): ) parser = CodeCheckerParser() findings = parser.get_findings(testfile, Test()) - self.assertTrue(94 == len(findings), str(len(findings))) + self.assertEqual(94, len(findings), str(len(findings))) - self.assertTrue(sum(1 for f in findings if f.duplicate) == 0) - self.assertTrue(sum(1 for f in findings if f.severity.upper() == 'HIGH') == 20) - self.assertTrue(sum(1 for f in findings if f.severity.upper() == 'INFO') == 6) - self.assertTrue(sum(1 for f in findings if f.severity.upper() == 'CRITICAL') == 0) - self.assertTrue(sum(1 for f in findings if f.severity.upper() == 'LOW') == 5) - self.assertTrue(sum(1 for f in findings if f.severity.upper() == 'MEDIUM') == 63) + self.assertEqual(sum(1 for f in findings if f.duplicate), 0) + self.assertEqual(sum(1 for f in findings if f.severity.upper() == 'HIGH'), 20) + self.assertEqual(sum(1 for f in findings if f.severity.upper() == 'INFO'), 6) + self.assertEqual(sum(1 for f in findings if f.severity.upper() == 'CRITICAL'), 0) + self.assertEqual(sum(1 for f in findings if f.severity.upper() == 'LOW'), 5) + self.assertEqual(sum(1 for f in findings if f.severity.upper() == 'MEDIUM'), 63) finding = findings[0] self.assertEqual("clang-diagnostic-sign-compare", finding.title) @@ -64,7 +64,7 @@ def test_parse_file_with_various_review_statuses(self): ) parser = CodeCheckerParser() findings = parser.get_findings(testfile, Test()) - self.assertTrue(len(findings) == 4) + self.assertEqual(len(findings), 4) finding = findings[0] self.assertTrue(finding.active) diff --git a/unittests/tools/test_coverity_api_parser.py b/unittests/tools/test_coverity_api_parser.py index 3da6f91fc81..3ec4423e621 100644 --- a/unittests/tools/test_coverity_api_parser.py +++ b/unittests/tools/test_coverity_api_parser.py @@ -7,10 +7,10 @@ class TestZapParser(DojoTestCase): def test_parse_wrong_file(self): - with self.assertRaises(ValueError) as ve: + with self.assertRaises(ValueError): testfile = open("unittests/scans/coverity_api/wrong.json") parser = CoverityApiParser() - findings = parser.get_findings(testfile, Test()) + parser.get_findings(testfile, Test()) def test_parse_no_findings(self): testfile = open("unittests/scans/coverity_api/empty.json") diff --git a/unittests/tools/test_dependency_track_parser.py b/unittests/tools/test_dependency_track_parser.py index 42f8fbd9dc8..4e0d203fe75 100644 --- a/unittests/tools/test_dependency_track_parser.py +++ b/unittests/tools/test_dependency_track_parser.py @@ -84,7 +84,7 @@ def test_dependency_track_parser_findings_with_alias(self): self.assertEqual(12, len(findings)) self.assertTrue(all(item.file_path is not None for item in findings)) self.assertTrue(all(item.vuln_id_from_tool is not None for item in findings)) - self.assertTrue('CVE-2022-42004' in findings[0].unsaved_vulnerability_ids) + self.assertIn('CVE-2022-42004', findings[0].unsaved_vulnerability_ids) def test_dependency_track_parser_findings_with_empty_alias(self): testfile = open( @@ -95,7 +95,7 @@ def test_dependency_track_parser_findings_with_empty_alias(self): testfile.close() self.assertEqual(12, len(findings)) - self.assertTrue('CVE-2022-2053' in findings[11].unsaved_vulnerability_ids) + self.assertIn('CVE-2022-2053', findings[11].unsaved_vulnerability_ids) def test_dependency_track_parser_findings_with_cvssV3_score(self): with open(f"{get_unit_tests_path()}/scans/dependency_track/many_findings_with_cvssV3_score.json") as testfile: @@ -104,5 +104,5 @@ def test_dependency_track_parser_findings_with_cvssV3_score(self): self.assertEqual(12, len(findings)) self.assertTrue(all(item.file_path is not None for item in findings)) self.assertTrue(all(item.vuln_id_from_tool is not None for item in findings)) - self.assertTrue('CVE-2022-42004' in findings[0].unsaved_vulnerability_ids) + self.assertIn('CVE-2022-42004', findings[0].unsaved_vulnerability_ids) self.assertEqual(8.3, findings[0].cvssv3_score) diff --git a/unittests/tools/test_dockerbench_parser.py b/unittests/tools/test_dockerbench_parser.py index 0c028d9a31c..02466d04a8e 100644 --- a/unittests/tools/test_dockerbench_parser.py +++ b/unittests/tools/test_dockerbench_parser.py @@ -35,11 +35,11 @@ def test_parse_file_with_multiple_vuln_has_multiple_findings(self): ) parser = DockerBenchParser() findings = parser.get_findings(testfile, Test()) - self.assertTrue(len(findings) == 50) - self.assertTrue(sum(1 for f in findings if f.severity.upper() == 'CRITICAL') == 0) - self.assertTrue(sum(1 for f in findings if f.severity.upper() == 'HIGH') == 32) - self.assertTrue(sum(1 for f in findings if f.severity.upper() == 'LOW') == 16) - self.assertTrue(sum(1 for f in findings if f.severity.upper() == 'INFO') == 2) + self.assertEqual(len(findings), 50) + self.assertEqual(sum(1 for f in findings if f.severity.upper() == 'CRITICAL'), 0) + self.assertEqual(sum(1 for f in findings if f.severity.upper() == 'HIGH'), 32) + self.assertEqual(sum(1 for f in findings if f.severity.upper() == 'LOW'), 16) + self.assertEqual(sum(1 for f in findings if f.severity.upper() == 'INFO'), 2) finding = findings[3] self.assertEqual("High", finding.severity) diff --git a/unittests/tools/test_gcloud_artifact_scan_parser.py b/unittests/tools/test_gcloud_artifact_scan_parser.py index fc829bf70c4..6da293f3489 100644 --- a/unittests/tools/test_gcloud_artifact_scan_parser.py +++ b/unittests/tools/test_gcloud_artifact_scan_parser.py @@ -8,7 +8,7 @@ def test_parse_file_with_multiple_vuln_has_multiple_findings(self): with open(f"{get_unit_tests_path()}/scans/gcloud_artifact_scan/many_vulns.json") as testfile: parser = GCloudArtifactScanParser() findings = parser.get_findings(testfile, Test()) - self.assertTrue(7, len(findings)) + self.assertEqual(7, len(findings)) finding = findings[0] self.assertEqual("projects/goog-vulnz/notes/CVE-2023-29405", finding.title) self.assertEqual("Critical", finding.severity) diff --git a/unittests/tools/test_generic_parser.py b/unittests/tools/test_generic_parser.py index 7fc12e6d20f..de4b8252ece 100644 --- a/unittests/tools/test_generic_parser.py +++ b/unittests/tools/test_generic_parser.py @@ -399,7 +399,7 @@ def test_missing_columns_is_fine(self): content = """Date,Title,Url,Severity,Description,References,Active,Verified""" file = TestFile("findings.csv", content) parser = GenericParser() - findings = parser.get_findings(file, self.test) + parser.get_findings(file, self.test) def test_column_order_is_flexible(self): content1 = """\ @@ -640,11 +640,11 @@ def test_parse_json_empty_finding(self): parser = GenericParser() with self.assertRaisesMessage(ValueError, "Required fields are missing: ['description', 'severity', 'title']"): - findings = parser.get_findings(file, Test()) + parser.get_findings(file, Test()) def test_parse_json_invalid_finding(self): file = open("unittests/scans/generic/generic_invalid.json") parser = GenericParser() with self.assertRaisesMessage(ValueError, "Not allowed fields are present: ['invalid_field', 'last_status_update']"): - findings = parser.get_findings(file, Test()) + parser.get_findings(file, Test()) diff --git a/unittests/tools/test_gitlab_dast_parser.py b/unittests/tools/test_gitlab_dast_parser.py index 5b915c91aba..353f7e73db5 100644 --- a/unittests/tools/test_gitlab_dast_parser.py +++ b/unittests/tools/test_gitlab_dast_parser.py @@ -82,7 +82,7 @@ def test_parse_file_with_multiple_vuln_has_multiple_findings_v14(self): finding = findings[1] # must-have fields self.assertEqual(3, finding.scanner_confidence) - self.assertTrue("Content Security Policy (CSP)" in finding.description) + self.assertIn("Content Security Policy (CSP)", finding.description) self.assertEqual(False, finding.static_finding) self.assertEqual(True, finding.dynamic_finding) @@ -95,14 +95,14 @@ def test_parse_file_with_multiple_vuln_has_multiple_findings_v14(self): # vulnerability does not have a name: fallback to using id as a title self.assertEqual(finding.unique_id_from_tool, finding.title) self.assertEqual(16, finding.cwe) - self.assertTrue("http://www.w3.org/TR/CSP/" in finding.references) + self.assertIn("http://www.w3.org/TR/CSP/", finding.references) self.assertEqual("Medium", finding.severity) endpoint = finding.unsaved_endpoints[0] self.assertEqual(str(endpoint), "http://api-server/v1/tree/10") self.assertEqual(endpoint.host, "api-server") # host port path self.assertEqual(endpoint.port, 80) self.assertEqual(endpoint.path, "v1/tree/10") - self.assertTrue("Ensure that your web server," in finding.mitigation) + self.assertIn("Ensure that your web server,", finding.mitigation) def test_parse_file_with_multiple_vuln_has_multiple_findings_v15(self): testfile = open("unittests/scans/gitlab_dast/gitlab_dast_many_vul_v15.json") @@ -120,7 +120,7 @@ def test_parse_file_with_multiple_vuln_has_multiple_findings_v15(self): finding = findings[1] # must-have fields self.assertEqual(None, finding.scanner_confidence) - self.assertTrue("Content Security Policy (CSP)" in finding.description) + self.assertIn("Content Security Policy (CSP)", finding.description) self.assertEqual(False, finding.static_finding) self.assertEqual(True, finding.dynamic_finding) @@ -133,11 +133,11 @@ def test_parse_file_with_multiple_vuln_has_multiple_findings_v15(self): # vulnerability does not have a name: fallback to using id as a title self.assertEqual(finding.unique_id_from_tool, finding.title) self.assertEqual(16, finding.cwe) - self.assertTrue("http://www.w3.org/TR/CSP/" in finding.references) + self.assertIn("http://www.w3.org/TR/CSP/", finding.references) self.assertEqual("Medium", finding.severity) endpoint = finding.unsaved_endpoints[0] self.assertEqual(str(endpoint), "http://api-server/v1/tree/10") self.assertEqual(endpoint.host, "api-server") # host port path self.assertEqual(endpoint.port, 80) self.assertEqual(endpoint.path, "v1/tree/10") - self.assertTrue("Ensure that your web server," in finding.mitigation) + self.assertIn("Ensure that your web server,", finding.mitigation) diff --git a/unittests/tools/test_gitlab_dep_scan_parser.py b/unittests/tools/test_gitlab_dep_scan_parser.py index 07171ca7fac..7e1a7f43ede 100644 --- a/unittests/tools/test_gitlab_dep_scan_parser.py +++ b/unittests/tools/test_gitlab_dep_scan_parser.py @@ -63,7 +63,7 @@ def test_parse_file_with_multiple_vuln_has_multiple_findings_v14(self): ) parser = GitlabDepScanParser() findings = parser.get_findings(testfile, Test()) - self.assertTrue(len(findings) > 2) + self.assertGreater(len(findings), 2) self.assertEqual(1, len(findings[0].unsaved_vulnerability_ids)) self.assertEqual("CVE-2020-29652", findings[0].unsaved_vulnerability_ids[0]) @@ -74,7 +74,7 @@ def test_parse_file_with_multiple_vuln_has_multiple_findings_v15(self): ) parser = GitlabDepScanParser() findings = parser.get_findings(testfile, Test()) - self.assertTrue(len(findings) > 2) + self.assertGreater(len(findings), 2) self.assertEqual(1, len(findings[0].unsaved_vulnerability_ids)) self.assertEqual("CVE-2020-29652", findings[0].unsaved_vulnerability_ids[0]) diff --git a/unittests/tools/test_gitlab_sast_parser.py b/unittests/tools/test_gitlab_sast_parser.py index b321ce9a6a2..779675592c9 100644 --- a/unittests/tools/test_gitlab_sast_parser.py +++ b/unittests/tools/test_gitlab_sast_parser.py @@ -33,7 +33,7 @@ def test_parse_file_with_multiple_vuln_has_multiple_findings_v14(self): with open(f"{get_unit_tests_path()}/scans/gitlab_sast/gl-sast-report-many-vuln_v14.json") as testfile: parser = GitlabSastParser() findings = parser.get_findings(testfile, Test()) - self.assertTrue(3, len(findings)) + self.assertEqual(219, len(findings)) finding = findings[0] self.assertEqual("Password in URL", finding.title) self.assertEqual("Critical", finding.severity) @@ -48,7 +48,7 @@ def test_parse_file_with_multiple_vuln_has_multiple_findings_v15(self): with open(f"{get_unit_tests_path()}/scans/gitlab_sast/gl-sast-report-many-vuln_v15.json") as testfile: parser = GitlabSastParser() findings = parser.get_findings(testfile, Test()) - self.assertTrue(3, len(findings)) + self.assertEqual(219, len(findings)) finding = findings[0] self.assertEqual("Password in URL", finding.title) self.assertEqual("Critical", finding.severity) @@ -63,9 +63,10 @@ def test_parse_file_with_various_confidences_v14(self): with open(f"{get_unit_tests_path()}/scans/gitlab_sast/gl-sast-report-confidence_v14.json") as testfile: parser = GitlabSastParser() findings = parser.get_findings(testfile, Test()) - self.assertTrue(len(findings) == 8) + self.assertEqual(len(findings), 8) for item in findings: - self.assertTrue(item.cwe is None or isinstance(item.cwe, int)) + if item.cwe: + self.assertIsInstance(item.cwe, int) finding = findings[3] self.assertEqual("Tentative", finding.get_scanner_confidence_text()) finding = findings[4] @@ -81,9 +82,10 @@ def test_parse_file_with_various_confidences_v15(self): with open(f"{get_unit_tests_path()}/scans/gitlab_sast/gl-sast-report-confidence_v15.json") as testfile: parser = GitlabSastParser() findings = parser.get_findings(testfile, Test()) - self.assertTrue(len(findings) == 8) + self.assertEqual(len(findings), 8) for item in findings: - self.assertTrue(item.cwe is None or isinstance(item.cwe, int)) + if item.cwe: + self.assertIsInstance(item.cwe, int) finding = findings[3] self.assertEqual("", finding.get_scanner_confidence_text()) finding = findings[4] @@ -99,7 +101,7 @@ def test_parse_file_with_various_cwes_v14(self): with open("unittests/scans/gitlab_sast/gl-sast-report-cwe_v14.json") as testfile: parser = GitlabSastParser() findings = parser.get_findings(testfile, Test()) - self.assertTrue(len(findings) == 3) + self.assertEqual(len(findings), 3) self.assertEqual(79, findings[0].cwe) self.assertEqual(89, findings[1].cwe) self.assertEqual(None, findings[2].cwe) @@ -108,7 +110,7 @@ def test_parse_file_with_various_cwes_v15(self): with open("unittests/scans/gitlab_sast/gl-sast-report-cwe_v15.json") as testfile: parser = GitlabSastParser() findings = parser.get_findings(testfile, Test()) - self.assertTrue(len(findings) == 3) + self.assertEqual(len(findings), 3) self.assertEqual(79, findings[0].cwe) self.assertEqual(89, findings[1].cwe) self.assertEqual(None, findings[2].cwe) diff --git a/unittests/tools/test_govulncheck_parser.py b/unittests/tools/test_govulncheck_parser.py index 55dc9bf84f0..b098cd7ab37 100644 --- a/unittests/tools/test_govulncheck_parser.py +++ b/unittests/tools/test_govulncheck_parser.py @@ -9,10 +9,10 @@ def test_parse_empty(self): with self.assertRaises(ValueError) as exp: testfile = open("unittests/scans/govulncheck/empty.json") parser = GovulncheckParser() - findings = parser.get_findings(testfile, Test()) - self.assertTrue( - "Invalid JSON format" in str(exp.exception) - ) + parser.get_findings(testfile, Test()) + self.assertIn( + "Invalid JSON format", str(exp.exception) + ) def test_parse_no_findings(self): testfile = open("unittests/scans/govulncheck/no_vulns.json") diff --git a/unittests/tools/test_immuniweb_parser.py b/unittests/tools/test_immuniweb_parser.py index 2673d8270ca..74b9e12d232 100644 --- a/unittests/tools/test_immuniweb_parser.py +++ b/unittests/tools/test_immuniweb_parser.py @@ -27,4 +27,4 @@ def test_parse_file_with_multiple_vuln_has_multiple_findings(self): for finding in findings: for endpoint in finding.unsaved_endpoints: endpoint.clean() - self.assertTrue(len(findings) > 2) + self.assertGreater(len(findings), 2) diff --git a/unittests/tools/test_intsights_parser.py b/unittests/tools/test_intsights_parser.py index 7e7e469bbcf..c091a00c2eb 100644 --- a/unittests/tools/test_intsights_parser.py +++ b/unittests/tools/test_intsights_parser.py @@ -62,7 +62,7 @@ def test_intsights_parser_invalid_text_with_error_csv(self): testfile = open( "unittests/scans/intsights/intsights_invalid_file.txt") parser = IntSightsParser() - findings = parser.get_findings(testfile, Test()) + parser.get_findings(testfile, Test()) def test_intsights_parser_with_no_alerts_json(self): testfile = open("unittests/scans/intsights/intsights_zero_vuln.json") diff --git a/unittests/tools/test_kubebench_parser.py b/unittests/tools/test_kubebench_parser.py index 4b3751c9841..0494e92ff3a 100644 --- a/unittests/tools/test_kubebench_parser.py +++ b/unittests/tools/test_kubebench_parser.py @@ -27,7 +27,7 @@ def test_parse_file_with_multiple_vuln_has_multiple_findings(self): ) parser = KubeBenchParser() findings = parser.get_findings(testfile, Test()) - self.assertTrue(len(findings) == 4) + self.assertEqual(len(findings), 4) def test_parse_file_with_controls_tag(self): diff --git a/unittests/tools/test_kubehunter_parser.py b/unittests/tools/test_kubehunter_parser.py index 63e74daf573..6c0683364a0 100644 --- a/unittests/tools/test_kubehunter_parser.py +++ b/unittests/tools/test_kubehunter_parser.py @@ -40,13 +40,12 @@ def test_kubehunter_parser_empty_with_error(self): with self.assertRaises(ValueError) as context: testfile = open("unittests/scans/kubehunter/empty.json") parser = KubeHunterParser() - findings = parser.get_findings(testfile, Test()) + parser.get_findings(testfile, Test()) testfile.close() - self.assertTrue( - "KubeHunter report contains errors:" in str(context.exception) - ) - self.assertTrue("ECONNREFUSED" in str(context.exception)) + self.assertEqual( + "Expecting value: line 1 column 1 (char 0)", str(context.exception) + ) def test_kubehunter_parser_dupe(self): testfile = open("unittests/scans/kubehunter/dupe.json") diff --git a/unittests/tools/test_meterian_parser.py b/unittests/tools/test_meterian_parser.py index 7108b2c16a1..ff2cf5d43ac 100644 --- a/unittests/tools/test_meterian_parser.py +++ b/unittests/tools/test_meterian_parser.py @@ -9,7 +9,7 @@ def test_meterianParser_invalid_security_report_raise_ValueError_exception(self) with self.assertRaises(ValueError): testfile = open("unittests/scans/meterian/report_invalid.json") parser = MeterianParser() - findings = parser.get_findings(testfile, Test()) + parser.get_findings(testfile, Test()) def test_meterianParser_report_has_no_finding(self): testfile = open("unittests/scans/meterian/report_no_vulns.json") @@ -61,13 +61,13 @@ def test_meterianParser_finding_has_fields(self): self.assertEqual("CVE-2020-26289", finding.unsaved_vulnerability_ids[0]) self.assertEqual(400, finding.cwe) self.assertTrue(finding.mitigation.startswith("## Remediation")) - self.assertTrue("Upgrade date-and-time to version 0.14.2 or higher." in finding.mitigation) - self.assertTrue("https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-26289" in finding.references, "found " + finding.references) - self.assertTrue("https://nvd.nist.gov/vuln/detail/CVE-2020-26289" in finding.references, "found " + finding.references) - self.assertTrue("https://www.npmjs.com/package/date-and-time" in finding.references, "found " + finding.references) - self.assertTrue("https://github.com/knowledgecode/date-and-time/security/advisories/GHSA-r92x-f52r-x54g" in finding.references, "found " + finding.references) - self.assertTrue("https://github.com/knowledgecode/date-and-time/commit/9e4b501eacddccc8b1f559fb414f48472ee17c2a" in finding.references, "found " + finding.references) - self.assertTrue("Manifest file", finding.file_path) + self.assertIn("Upgrade date-and-time to version 0.14.2 or higher.", finding.mitigation) + self.assertIn("https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-26289", finding.references, "found " + finding.references) + self.assertIn("https://nvd.nist.gov/vuln/detail/CVE-2020-26289", finding.references, "found " + finding.references) + self.assertIn("https://www.npmjs.com/package/date-and-time", finding.references, "found " + finding.references) + self.assertIn("https://github.com/knowledgecode/date-and-time/security/advisories/GHSA-r92x-f52r-x54g", finding.references, "found " + finding.references) + self.assertIn("https://github.com/knowledgecode/date-and-time/commit/9e4b501eacddccc8b1f559fb414f48472ee17c2a", finding.references, "found " + finding.references) + self.assertIn("Manifest file", finding.file_path) self.assertEqual(["nodejs"], finding.tags) def test_meterianParser_finding_has_no_remediation(self): @@ -79,8 +79,8 @@ def test_meterianParser_finding_has_no_remediation(self): finding = findings[0] self.assertTrue(finding.mitigation.startswith("We were not able to provide a safe version for this library.")) - self.assertTrue("You should consider replacing this component as it could be an " + - "issue for the safety of your application." in finding.mitigation) + self.assertIn("You should consider replacing this component as it could be an " + + "issue for the safety of your application.", finding.mitigation) def test_meterianParser_dual_language_report_has_two_findins(self): testfile = open("unittests/scans/meterian/report_multi_language.json") diff --git a/unittests/tools/test_nikto_parser.py b/unittests/tools/test_nikto_parser.py index 6fba5a6fd72..b7037fe364b 100644 --- a/unittests/tools/test_nikto_parser.py +++ b/unittests/tools/test_nikto_parser.py @@ -48,7 +48,7 @@ def test_parse_file_with_multiple_vuln_has_multiple_findings(self): for finding in findings: for endpoint in finding.unsaved_endpoints: endpoint.clean() - self.assertTrue(len(findings) == 10) + self.assertEqual(len(findings), 10) def test_parse_file_json_with_multiple_vuln_has_multiple_findings(self): testfile = open("unittests/scans/nikto/juice-shop.json") diff --git a/unittests/tools/test_npm_audit_parser.py b/unittests/tools/test_npm_audit_parser.py index 189d8a7879f..10149ca1c10 100644 --- a/unittests/tools/test_npm_audit_parser.py +++ b/unittests/tools/test_npm_audit_parser.py @@ -74,19 +74,18 @@ def test_npm_audit_parser_empty_with_error(self): with self.assertRaises(ValueError) as context: testfile = open(path.join(path.dirname(__file__), "../scans/npm_audit/empty_with_error.json")) parser = NpmAuditParser() - findings = parser.get_findings(testfile, Test()) + parser.get_findings(testfile, Test()) testfile.close() - self.assertTrue("npm audit report contains errors:" in str(context.exception)) - self.assertTrue("ENOAUDIT" in str(context.exception)) + self.assertIn("npm audit report contains errors:", str(context.exception)) + self.assertIn("ENOAUDIT", str(context.exception)) def test_npm_audit_parser_many_vuln_npm7(self): with self.assertRaises(ValueError) as context: testfile = open(path.join(path.dirname(__file__), "../scans/npm_audit/many_vuln_npm7.json")) parser = NpmAuditParser() - findings = parser.get_findings(testfile, Test()) + parser.get_findings(testfile, Test()) testfile.close() - self.assertTrue("npm7 with auditReportVersion 2 or higher not yet supported" in str(context.exception)) - self.assertEqual(findings, None) + self.assertIn("npm7 with auditReportVersion 2 or higher not yet supported", str(context.exception)) def test_npm_audit_censored_hash(self): path = "77d76e075ae87483063c4c74885422f98300f9fc0ecbd3b8dfb60152a36e5269>axios" diff --git a/unittests/tools/test_nsp_parser.py b/unittests/tools/test_nsp_parser.py index 81d661499eb..469b0b117de 100644 --- a/unittests/tools/test_nsp_parser.py +++ b/unittests/tools/test_nsp_parser.py @@ -27,12 +27,12 @@ def test_parse_ok(self): codeExec += 1 elif finding.title.startswith("Regular Expression Denial of Service"): self.assertEqual(findings[0].severity, "High") - self.assertTrue( - finding.references == "https://nodesecurity.io/advisories/106" or - finding.references == "https://nodesecurity.io/advisories/526" or - finding.references == "https://nodesecurity.io/advisories/534" or - finding.references == "https://nodesecurity.io/advisories/535" - ) + self.assertIn(finding.references, [ + "https://nodesecurity.io/advisories/106", + "https://nodesecurity.io/advisories/526", + "https://nodesecurity.io/advisories/534", + "https://nodesecurity.io/advisories/535", + ]) dos += 1 else: self.fail("Unexpected NSP finding.") diff --git a/unittests/tools/test_ossindex_devaudit_parser.py b/unittests/tools/test_ossindex_devaudit_parser.py index 550d19d0389..841730dbf84 100644 --- a/unittests/tools/test_ossindex_devaudit_parser.py +++ b/unittests/tools/test_ossindex_devaudit_parser.py @@ -30,7 +30,7 @@ def test_ossindex_devaudit_parser_with_multiple_vulns_has_multiple_finding(self) parser = OssIndexDevauditParser() findings = parser.get_findings(testfile, Test()) testfile.close() - self.assertTrue(len(findings) > 1) + self.assertGreater(len(findings), 1) def test_ossindex_devaudit_parser_with_no_cve_returns_info_severity(self): testfile = open( @@ -39,7 +39,7 @@ def test_ossindex_devaudit_parser_with_no_cve_returns_info_severity(self): parser = OssIndexDevauditParser() findings = parser.get_findings(testfile, Test()) testfile.close() - self.assertTrue(len(findings) == 1) + self.assertEqual(len(findings), 1) def test_ossindex_devaudit_parser_with_reference_shows_reference(self): testfile = open( @@ -51,7 +51,7 @@ def test_ossindex_devaudit_parser_with_reference_shows_reference(self): if len(findings) > 0: for item in findings: - self.assertTrue(item.references != "") + self.assertNotEqual(item.references, "") def test_ossindex_devaudit_parser_with_empty_reference_shows_empty_reference(self): testfile = open( @@ -62,7 +62,7 @@ def test_ossindex_devaudit_parser_with_empty_reference_shows_empty_reference(sel testfile.close() if len(findings) > 0: for item in findings: - self.assertTrue(item.references == "") + self.assertEqual(item.references, "") def test_ossindex_devaudit_parser_with_missing_reference_shows_empty(self): testfile = open( @@ -73,7 +73,7 @@ def test_ossindex_devaudit_parser_with_missing_reference_shows_empty(self): testfile.close() if len(findings) > 0: for item in findings: - self.assertTrue(item.references == "") + self.assertEqual(item.references, "") def test_ossindex_devaudit_parser_with_missing_cwe_shows_1035(self): testfile = open( @@ -84,7 +84,7 @@ def test_ossindex_devaudit_parser_with_missing_cwe_shows_1035(self): testfile.close() if len(findings) > 0: for item in findings: - self.assertTrue(item.cwe == 1035) + self.assertEqual(item.cwe, 1035) def test_ossindex_devaudit_parser_with_null_cwe_shows_1035(self): testfile = open( @@ -95,7 +95,7 @@ def test_ossindex_devaudit_parser_with_null_cwe_shows_1035(self): testfile.close() if len(findings) > 0: for item in findings: - self.assertTrue(item.cwe == 1035) + self.assertEqual(item.cwe, 1035) def test_ossindex_devaudit_parser_with_empty_cwe_shows_1035(self): testfile = open( @@ -106,7 +106,7 @@ def test_ossindex_devaudit_parser_with_empty_cwe_shows_1035(self): testfile.close() if len(findings) > 0: for item in findings: - self.assertTrue(item.cwe == 1035) + self.assertEqual(item.cwe, 1035) def test_ossindex_devaudit_parser_get_severity_shows_info(self): testfile = open( @@ -117,7 +117,7 @@ def test_ossindex_devaudit_parser_get_severity_shows_info(self): testfile.close() if len(findings) > 0: for item in findings: - self.assertTrue(item.severity == "Info") + self.assertEqual(item.severity, "Info") def test_ossindex_devaudit_parser_get_severity_shows_critical(self): testfile = open( @@ -128,7 +128,7 @@ def test_ossindex_devaudit_parser_get_severity_shows_critical(self): testfile.close() if len(findings) > 0: for item in findings: - self.assertTrue(item.severity == "Critical") + self.assertEqual(item.severity, "Critical") def test_ossindex_devaudit_parser_get_severity_shows_high(self): testfile = open( @@ -139,7 +139,7 @@ def test_ossindex_devaudit_parser_get_severity_shows_high(self): testfile.close() if len(findings) > 0: for item in findings: - self.assertTrue(item.severity == "High") + self.assertEqual(item.severity, "High") def test_ossindex_devaudit_parser_get_severity_shows_medium(self): testfile = open( @@ -150,7 +150,7 @@ def test_ossindex_devaudit_parser_get_severity_shows_medium(self): testfile.close() if len(findings) > 0: for item in findings: - self.assertTrue(item.severity == "Medium") + self.assertEqual(item.severity, "Medium") def test_ossindex_devaudit_parser_get_severity_shows_low(self): testfile = open( @@ -161,4 +161,4 @@ def test_ossindex_devaudit_parser_get_severity_shows_low(self): testfile.close() if len(findings) > 0: for item in findings: - self.assertTrue(item.severity == "Low") + self.assertEqual(item.severity, "Low") diff --git a/unittests/tools/test_risk_recon_parser.py b/unittests/tools/test_risk_recon_parser.py index 62ec6306364..d2394d1dfa5 100644 --- a/unittests/tools/test_risk_recon_parser.py +++ b/unittests/tools/test_risk_recon_parser.py @@ -11,13 +11,13 @@ def test_api_with_bad_url(self): testfile = open("unittests/scans/risk_recon/bad_url.json") with self.assertRaises(Exception): parser = RiskReconParser() - findings = parser.get_findings(testfile, Test()) + parser.get_findings(testfile, Test()) def test_api_with_bad_key(self): testfile = open("unittests/scans/risk_recon/bad_key.json") with self.assertRaises(Exception): parser = RiskReconParser() - findings = parser.get_findings(testfile, Test()) + parser.get_findings(testfile, Test()) def test_parser_without_api(self): testfile = open("unittests/scans/risk_recon/findings.json") diff --git a/unittests/tools/test_ssl_labs_parser.py b/unittests/tools/test_ssl_labs_parser.py index 4591b58d718..807dc049917 100644 --- a/unittests/tools/test_ssl_labs_parser.py +++ b/unittests/tools/test_ssl_labs_parser.py @@ -22,7 +22,7 @@ def test_parse_ok(self): self.assertEqual(findings[0].unsaved_endpoints[0].host, "defectdojo.mevitae.com") self.assertEqual(findings[0].cwe, 310) self.assertEqual(findings[0].severity, "Info") - self.assertTrue("TLS" in findings[0].description) + self.assertIn("TLS", findings[0].description) def test_parse_dh1024(self): parser = SslLabsParser() @@ -37,7 +37,7 @@ def test_parse_dh1024(self): self.assertEqual(findings[0].unsaved_endpoints[0].host, "dh1024.badssl.com") self.assertEqual(findings[0].cwe, 310) self.assertEqual(findings[0].severity, "Medium") - self.assertTrue("TLS" in findings[0].description) + self.assertIn("TLS", findings[0].description) def test_parse_3des(self): parser = SslLabsParser() @@ -52,7 +52,7 @@ def test_parse_3des(self): self.assertEqual(findings[0].unsaved_endpoints[0].host, "3des.badssl.com") self.assertEqual(findings[0].cwe, 310) self.assertEqual(findings[0].severity, "High") - self.assertTrue("TLS" in findings[0].description) + self.assertIn("TLS", findings[0].description) def test_parse_revoked(self): parser = SslLabsParser() @@ -67,7 +67,7 @@ def test_parse_revoked(self): self.assertEqual(findings[0].unsaved_endpoints[0].host, "revoked.badssl.com") self.assertEqual(findings[0].cwe, 310) self.assertEqual(findings[0].severity, "Critical") - self.assertTrue("TLS" in findings[0].description) + self.assertIn("TLS", findings[0].description) def test_parse_multiple(self): parser = SslLabsParser() @@ -84,7 +84,7 @@ def test_parse_multiple(self): foundCritical = False for finding in findings: - self.assertTrue("TLS" in finding.description) + self.assertIn("TLS", finding.description) self.assertEqual(finding.cwe, 310) if finding.severity == "Info": self.assertEqual(finding.title, "TLS Grade 'A+' for defectdojo.mevitae.com") diff --git a/unittests/tools/test_sysdig_reports_parser.py b/unittests/tools/test_sysdig_reports_parser.py index 260ba88a0ac..98d30fcfc25 100644 --- a/unittests/tools/test_sysdig_reports_parser.py +++ b/unittests/tools/test_sysdig_reports_parser.py @@ -44,10 +44,9 @@ def test_sysdig_parser_missing_cve_field_id_from_csv_file(self): for finding in findings: for endpoint in finding.unsaved_endpoints: endpoint.clean() - self.assertTrue( - "sysdig report contains errors:" in str(context.exception) - ) - self.assertTrue("ECONNREFUSED" in str(context.exception)) + self.assertEqual( + "Number of fields in row (22) does not match number of headers (21)", str(context.exception) + ) def test_sysdig_parser_missing_cve_field_not_starting_with_cve(self): with self.assertRaises(ValueError) as context: @@ -58,10 +57,9 @@ def test_sysdig_parser_missing_cve_field_not_starting_with_cve(self): for finding in findings: for endpoint in finding.unsaved_endpoints: endpoint.clean() - self.assertTrue( - "sysdig report contains errors:" in str(context.exception) - ) - self.assertTrue("ECONNREFUSED" in str(context.exception)) + self.assertEqual( + "Number of fields in row (22) does not match number of headers (21)", str(context.exception) + ) def test_sysdig_parser_json_with_many_findings(self): testfile = open("unittests/scans/sysdig_reports/sysdig.json") diff --git a/unittests/tools/test_tenable_parser.py b/unittests/tools/test_tenable_parser.py index 4f8176fda37..482d46cdad6 100644 --- a/unittests/tools/test_tenable_parser.py +++ b/unittests/tools/test_tenable_parser.py @@ -70,7 +70,7 @@ def test_parse_some_findings_csv2_nessus_legacy(self): finding = findings[0] self.assertIn(finding.severity, Finding.SEVERITIES) self.assertEqual("Info", finding.severity) - self.assertFalse(finding.unsaved_vulnerability_ids) + self.assertEqual(0, len(finding.unsaved_vulnerability_ids)) self.assertEqual(0, finding.cwe) self.assertEqual("HTTP Server Type and Version", finding.title) finding = findings[25] @@ -92,7 +92,7 @@ def test_parse_some_findings_csv2_all_nessus_legacy(self): finding = findings[0] self.assertIn(finding.severity, Finding.SEVERITIES) self.assertEqual("Info", finding.severity) - self.assertFalse(finding.unsaved_vulnerability_ids) + self.assertEqual(0, len(finding.unsaved_vulnerability_ids)) self.assertEqual(0, finding.cwe) self.assertEqual("HTTP Server Type and Version", finding.title) finding = findings[25] @@ -136,14 +136,14 @@ def test_parse_some_findings_samples_nessus_legacy(self): finding = findings[0] self.assertIn(finding.severity, Finding.SEVERITIES) self.assertEqual("Info", finding.severity) - self.assertFalse(finding.unsaved_vulnerability_ids) + self.assertEqual(0, len(finding.unsaved_vulnerability_ids)) self.assertEqual("Nessus Scan Information", finding.title) finding = findings[25] self.assertIn(finding.severity, Finding.SEVERITIES) self.assertEqual("Nessus SYN scanner", finding.title) self.assertEqual("Info", finding.severity) - self.assertFalse(finding.unsaved_vulnerability_ids) + self.assertEqual(0, len(finding.unsaved_vulnerability_ids)) endpoint = finding.unsaved_endpoints[26] self.assertEqual("http", endpoint.protocol) endpoint = finding.unsaved_endpoints[37] @@ -221,7 +221,7 @@ def test_parse_many_findings_csv_nessus_was_legacy(self): finding = findings[i] self.assertIn(finding.severity, Finding.SEVERITIES) self.assertEqual('google.com', finding.unsaved_endpoints[0].host) - self.assertFalse(finding.unsaved_vulnerability_ids) + self.assertEqual(0, len(finding.unsaved_vulnerability_ids)) finding = findings[0] self.assertEqual('7.1', finding.cvssv3_score) self.assertEqual('High', finding.severity) @@ -238,7 +238,7 @@ def test_parse_one_findings_csv_nessus_was_legacy(self): finding = findings[0] self.assertIn(finding.severity, Finding.SEVERITIES) self.assertEqual('google.com', finding.unsaved_endpoints[0].host) - self.assertFalse(finding.unsaved_vulnerability_ids) + self.assertEqual(0, len(finding.unsaved_vulnerability_ids)) self.assertEqual('7.1', finding.cvssv3_score) self.assertEqual('High', finding.severity) self.assertEqual('http', finding.unsaved_endpoints[0].protocol) diff --git a/unittests/tools/test_threagile_parser.py b/unittests/tools/test_threagile_parser.py index 0a516e3fdcc..396907be4d2 100644 --- a/unittests/tools/test_threagile_parser.py +++ b/unittests/tools/test_threagile_parser.py @@ -9,8 +9,8 @@ def test_non_threagile_file_raises_error(self): parser = ThreagileParser() with self.assertRaises(ValueError) as exc_context: parser.get_findings(testfile, Test()) - exc = exc_context.exception - self.assertEqual("Invalid ThreAgile risks file", str(exc)) + exc = exc_context.exception + self.assertEqual("Invalid ThreAgile risks file", str(exc)) def test_empty_file_returns_no_findings(self): with open("unittests/scans/threagile/empty_file_no_risks.json") as testfile: diff --git a/unittests/tools/test_trivy_operator_parser.py b/unittests/tools/test_trivy_operator_parser.py index f3a1db7d9eb..60b012646cb 100644 --- a/unittests/tools/test_trivy_operator_parser.py +++ b/unittests/tools/test_trivy_operator_parser.py @@ -121,3 +121,18 @@ def test_exposedsecretreport_many(self): self.assertEqual("github-pat", finding.references) self.assertEqual("root/github_secret.txt", finding.file_path) self.assertEqual("Secret detected in root/github_secret.txt - GitHub Personal Access Token", finding.title) + + def test_vulnerabilityreport_extended(self): + test_file = open(sample_path("vulnerabilityreport_extended.json")) + parser = TrivyOperatorParser() + findings = parser.get_findings(test_file, Test()) + self.assertEqual(len(findings), 5) + finding = findings[0] + self.assertEqual("Medium", finding.severity) + self.assertEqual(1, len(finding.unsaved_vulnerability_ids)) + self.assertEqual("CVE-2024-0553", finding.unsaved_vulnerability_ids[0]) + self.assertEqual("CVE-2024-0553 libgnutls30 3.6.13-2ubuntu1.9", finding.title) + self.assertEqual("3.6.13-2ubuntu1.10", finding.mitigation) + self.assertEqual(5.9, finding.cvssv3_score) + self.assertEqual("ubuntu:20.04 (ubuntu 20.04)", finding.file_path) + self.assertEqual("os-pkgs, ubuntu", str(finding.tags)) diff --git a/unittests/tools/test_trufflehog3_parser.py b/unittests/tools/test_trufflehog3_parser.py index 43482e5f3bd..0f1b2c20488 100644 --- a/unittests/tools/test_trufflehog3_parser.py +++ b/unittests/tools/test_trufflehog3_parser.py @@ -99,3 +99,9 @@ def test_many_vulns_current(self): self.assertEqual('env-file.txt', finding.file_path) self.assertEqual(10, finding.line) self.assertEqual(1, finding.nb_occurences) + + def test_issue_6999(self): + test_file = open(sample_path("issue_6999.json")) + parser = TruffleHog3Parser() + findings = parser.get_findings(test_file, Test()) + self.assertEqual(len(findings), 1) diff --git a/unittests/tools/test_veracode_parser.py b/unittests/tools/test_veracode_parser.py index 878629e6b7b..55799e9cf83 100644 --- a/unittests/tools/test_veracode_parser.py +++ b/unittests/tools/test_veracode_parser.py @@ -156,8 +156,6 @@ def parse_file_with_mitigated_finding(self): self.assertEqual(datetime.datetime(2020, 6, 1, 10, 2, 1), finding.mitigated) self.assertEqual("app-1234_issue-1", finding.unique_id_from_tool) self.assertEqual(0, finding.sla_age) - self.assertEqual(90, finding.sla_days_remaining()) - self.assertEqual((datetime.datetime(2020, 6, 1, 10, 2, 1) + datetime.timedelta(days=90)).date(), finding.sla_deadline()) @override_settings(USE_FIRST_SEEN=True) def test_parse_file_with_mitigated_fixed_finding_first_seen(self): diff --git a/unittests/tools/test_wazuh_parser.py b/unittests/tools/test_wazuh_parser.py new file mode 100644 index 00000000000..1b64ed6dd83 --- /dev/null +++ b/unittests/tools/test_wazuh_parser.py @@ -0,0 +1,51 @@ +from ..dojo_test_case import DojoTestCase +from dojo.tools.wazuh.parser import WazuhParser +from dojo.models import Test + + +class TestWazuhParser(DojoTestCase): + + def test_parse_no_findings(self): + testfile = open("unittests/scans/wazuh/no_findings.json") + parser = WazuhParser() + findings = parser.get_findings(testfile, Test()) + self.assertEqual(0, len(findings)) + + def test_parse_one_finding(self): + testfile = open("unittests/scans/wazuh/one_finding.json") + parser = WazuhParser() + findings = parser.get_findings(testfile, Test()) + for finding in findings: + for endpoint in finding.unsaved_endpoints: + endpoint.clean() + self.assertEqual(1, len(findings)) + self.assertEqual("Medium", finding.severity) + self.assertEqual("CVE-1234-123123", finding.unsaved_vulnerability_ids) + self.assertEqual("asdf", finding.component_name) + self.assertEqual("4.3.1", finding.component_version) + self.assertEqual(5.5, finding.cvssv3_score) + + def test_parse_many_finding(self): + testfile = open("unittests/scans/wazuh/many_findings.json") + parser = WazuhParser() + findings = parser.get_findings(testfile, Test()) + for finding in findings: + for endpoint in finding.unsaved_endpoints: + endpoint.clean() + self.assertEqual(6, len(findings)) + + def test_parse_one_finding_with_endpoint(self): + testfile = open("unittests/scans/wazuh/one_finding_with_endpoint.json") + parser = WazuhParser() + findings = parser.get_findings(testfile, Test()) + for finding in findings: + for endpoint in finding.unsaved_endpoints: + endpoint.clean() + self.assertEqual(1, len(findings)) + self.assertEqual("Medium", finding.severity) + self.assertEqual("CVE-1234-1234", finding.unsaved_vulnerability_ids) + self.assertEqual(6.5, finding.cvssv3_score) + endpoint = finding.unsaved_endpoints[0] + self.assertEqual("agent-1", endpoint.host) + self.assertEqual("asdf", finding.component_name) + self.assertEqual("1", finding.component_version) diff --git a/unittests/tools/test_wfuzz_parser.py b/unittests/tools/test_wfuzz_parser.py index ff34c93788b..ef826921f9d 100644 --- a/unittests/tools/test_wfuzz_parser.py +++ b/unittests/tools/test_wfuzz_parser.py @@ -37,3 +37,13 @@ def test_one_dup_finding(self): for endpoint in finding.unsaved_endpoints: endpoint.clean() self.assertEqual(4, len(findings)) + + def test_issue_7863(self): + testfile = open("unittests/scans/wfuzz/issue_7863.json") + parser = WFuzzParser() + findings = parser.get_findings(testfile, Test()) + for finding in findings: + for endpoint in finding.unsaved_endpoints: + endpoint.clean() + self.assertEqual(1, len(findings)) + self.assertEqual("Medium", findings[0].severity) diff --git a/unittests/tools/test_yarn_audit_parser.py b/unittests/tools/test_yarn_audit_parser.py index 0fb5c64d496..d8872259460 100644 --- a/unittests/tools/test_yarn_audit_parser.py +++ b/unittests/tools/test_yarn_audit_parser.py @@ -69,9 +69,17 @@ def test_yarn_audit_parser_empty_with_error(self): with self.assertRaises(ValueError) as context: testfile = open("unittests/scans/yarn_audit/empty_with_error.json") parser = YarnAuditParser() - findings = parser.get_findings(testfile, self.get_test()) + parser.get_findings(testfile, self.get_test()) testfile.close() self.assertTrue( "yarn audit report contains errors:" in str(context.exception) ) self.assertTrue("ECONNREFUSED" in str(context.exception)) + + def test_yarn_audit_parser_issue_6495(self): + testfile = open("unittests/scans/yarn_audit/issue_6495.json") + parser = YarnAuditParser() + findings = parser.get_findings(testfile, self.get_test()) + testfile.close() + self.assertEqual(3, len(findings)) + self.assertEqual(findings[0].cwe, "1321") From 8604a72204c091bd7e3d44904167571a6783b5aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quirin=20Zie=C3=9Fler?= <19915467+quirinziessler@users.noreply.github.com> Date: Sun, 11 Feb 2024 15:12:12 +0100 Subject: [PATCH 14/22] updated migrations --- ...y => 0201_add_epss_score_and_epps_percentile_to_findings.py} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename dojo/db_migrations/{0200_add_epss_score_and_epps_percentile_to_findings.py => 0201_add_epss_score_and_epps_percentile_to_findings.py} (94%) diff --git a/dojo/db_migrations/0200_add_epss_score_and_epps_percentile_to_findings.py b/dojo/db_migrations/0201_add_epss_score_and_epps_percentile_to_findings.py similarity index 94% rename from dojo/db_migrations/0200_add_epss_score_and_epps_percentile_to_findings.py rename to dojo/db_migrations/0201_add_epss_score_and_epps_percentile_to_findings.py index fda651aee0f..8844807220c 100644 --- a/dojo/db_migrations/0200_add_epss_score_and_epps_percentile_to_findings.py +++ b/dojo/db_migrations/0201_add_epss_score_and_epps_percentile_to_findings.py @@ -6,7 +6,7 @@ class Migration(migrations.Migration): dependencies = [ - ('dojo', '0199_whitesource_to_mend'), + ('dojo', '0200_finding_sla_expiration_date_product_async_updating_and_more'), ] operations = [ From 2380cc4f017542af19445b9f3b149866a63c1677 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quirin=20Zie=C3=9Fler?= <19915467+quirinziessler@users.noreply.github.com> Date: Sun, 11 Feb 2024 15:30:37 +0100 Subject: [PATCH 15/22] added percentage to findings_list --- dojo/templates/dojo/findings_list_snippet.html | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/dojo/templates/dojo/findings_list_snippet.html b/dojo/templates/dojo/findings_list_snippet.html index 95abfa7c489..514c6e1a96c 100644 --- a/dojo/templates/dojo/findings_list_snippet.html +++ b/dojo/templates/dojo/findings_list_snippet.html @@ -2,6 +2,7 @@ {% load display_tags %} {% load authorization_tags %} {% load get_endpoint_status %} +{% load multiply %} {% load static %} {% load i18n %} {% block findings_list %} @@ -323,10 +324,7 @@

    {% trans "Vulnerability Id" %} - {% trans "EPSS Score" %} - - - {% trans "EPSS Percentile" %} + {% trans "EPSS Score" %} / {% trans "Percentile" %} {% if filter_name == 'Closed' %} @@ -600,15 +598,14 @@

    {% endwith %} - {% if finding.epss_score %} - {{ finding.epss_score }} + {% if finding.epss_score is not None %} + {{ finding.epss_score|multiply:100|floatformat:"2" }}% {% else %} N.A. {% endif %} - - - {% if finding.epss_percentile %} - {{ finding.epss_percentile }} + / + {% if finding.epss_percentile is not None %} + {{ finding.epss_percentile|multiply:100|floatformat:"2" }}% {% else %} N.A. {% endif %} @@ -741,6 +738,7 @@

    }}, { "data": "cwe" }, { "data": "cve" }, + { "data": "epss"}, { "data": "found_date" }, { "data": "finding_age" }, {% if system_settings.enable_finding_sla %} From 49474b5ae04ccb927116f01023669cda8fb4609a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quirin=20Zie=C3=9Fler?= <19915467+quirinziessler@users.noreply.github.com> Date: Sun, 11 Feb 2024 15:33:30 +0100 Subject: [PATCH 16/22] :pencil2: tightening column title in findings detail page --- dojo/templates/dojo/view_finding.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dojo/templates/dojo/view_finding.html b/dojo/templates/dojo/view_finding.html index d8fec067ecb..9517bc0eab3 100755 --- a/dojo/templates/dojo/view_finding.html +++ b/dojo/templates/dojo/view_finding.html @@ -273,7 +273,7 @@

    Vulnerability Id {% if finding.epss_score != None or finding.epss_percentile != None %} {% if finding.epss_score != None and finding.epss_percentile != None %} - EPSS Score / EPSS Percentile + EPSS Score / Percentile {% elif finding.epss_score != None and finding.epss_percentile == None %} EPSS Score {% elif finding.epss_score == None and finding.epss_percentile != None %} From 556f18f32d80f60a43f02d9add7db20a403e62f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quirin=20Zie=C3=9Fler?= <19915467+quirinziessler@users.noreply.github.com> Date: Sun, 11 Feb 2024 15:35:04 +0100 Subject: [PATCH 17/22] flake8 --- dojo/templatetags/multiply.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dojo/templatetags/multiply.py b/dojo/templatetags/multiply.py index fc8b8b24aa1..641fa4889cc 100644 --- a/dojo/templatetags/multiply.py +++ b/dojo/templatetags/multiply.py @@ -2,6 +2,7 @@ register = template.Library() + @register.filter def multiply(value, arg): - return value * arg \ No newline at end of file + return value * arg From 394025c0012993622a7bde5fadd3f2951108d646 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quirin=20Zie=C3=9Fler?= <19915467+quirinziessler@users.noreply.github.com> Date: Sun, 11 Feb 2024 15:37:08 +0100 Subject: [PATCH 18/22] undo DT parser update --- dojo/tools/dependency_track/parser.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/dojo/tools/dependency_track/parser.py b/dojo/tools/dependency_track/parser.py index 965e3e32362..c808c820a92 100644 --- a/dojo/tools/dependency_track/parser.py +++ b/dojo/tools/dependency_track/parser.py @@ -211,17 +211,6 @@ def _convert_dependency_track_finding_to_dojo_finding(self, dependency_track_fin analysis = dependency_track_finding.get('analysis') is_false_positive = True if analysis is not None and analysis.get('state') == 'FALSE_POSITIVE' else False - # Get the EPSS details - if 'epssPercentile' in dependency_track_finding['vulnerability']: - epss_percentile = dependency_track_finding['vulnerability']['epssPercentile'] - else: - epss_percentile = None - - if 'epssScore' in dependency_track_finding['vulnerability']: - epss_score = dependency_track_finding['vulnerability']['epssScore'] - else: - epss_score = None - # Build and return Finding model finding = Finding( title=title, @@ -247,11 +236,6 @@ def _convert_dependency_track_finding_to_dojo_finding(self, dependency_track_fin if cvss_score: finding.cvssv3_score = cvss_score - if epss_score: - finding.epss_score = epss_score - if epss_percentile: - finding.epss_percentile = epss_percentile - return finding def get_scan_types(self): From 337da482cd209a2d5c71358508dd4fc3517bba5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quirin=20Zie=C3=9Fler?= <19915467+quirinziessler@users.noreply.github.com> Date: Sun, 11 Feb 2024 16:34:14 +0100 Subject: [PATCH 19/22] fix migrations --- ...er_finding_options_finding_epss_percentile_and_more.py} | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) rename dojo/db_migrations/{0201_add_epss_score_and_epps_percentile_to_findings.py => 0201_alter_finding_options_finding_epss_percentile_and_more.py} (76%) diff --git a/dojo/db_migrations/0201_add_epss_score_and_epps_percentile_to_findings.py b/dojo/db_migrations/0201_alter_finding_options_finding_epss_percentile_and_more.py similarity index 76% rename from dojo/db_migrations/0201_add_epss_score_and_epps_percentile_to_findings.py rename to dojo/db_migrations/0201_alter_finding_options_finding_epss_percentile_and_more.py index 8844807220c..abde0699aa9 100644 --- a/dojo/db_migrations/0201_add_epss_score_and_epps_percentile_to_findings.py +++ b/dojo/db_migrations/0201_alter_finding_options_finding_epss_percentile_and_more.py @@ -1,5 +1,6 @@ -# Generated by Django 4.1.13 on 2024-02-06 14:28 +# Generated by Django 4.1.13 on 2024-02-11 15:32 +import django.core.validators from django.db import migrations, models @@ -17,12 +18,12 @@ class Migration(migrations.Migration): migrations.AddField( model_name='finding', name='epss_percentile', - field=models.FloatField(blank=True, default=None, help_text='EPSS percentile for the CVE. Describes how many CVEs are scored at or below this one.', null=True, verbose_name='EPSS percentile'), + field=models.FloatField(blank=True, default=None, help_text='EPSS percentile for the CVE. Describes how many CVEs are scored at or below this one.', null=True, validators=[django.core.validators.MinValueValidator(0.0), django.core.validators.MaxValueValidator(1.0)], verbose_name='EPSS percentile'), ), migrations.AddField( model_name='finding', name='epss_score', - field=models.FloatField(blank=True, default=None, help_text='EPSS score for the CVE. Describes how likely it is the vulnerability will be exploited in the next 30 days.', null=True, verbose_name='EPSS Score'), + field=models.FloatField(blank=True, default=None, help_text='EPSS score for the CVE. Describes how likely it is the vulnerability will be exploited in the next 30 days.', null=True, validators=[django.core.validators.MinValueValidator(0.0), django.core.validators.MaxValueValidator(1.0)], verbose_name='EPSS Score'), ), migrations.AddIndex( model_name='finding', From 8d71ee4884a33b12e6b71711c74f3ad572e09b53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quirin=20Zie=C3=9Fler?= <19915467+quirinziessler@users.noreply.github.com> Date: Thu, 15 Feb 2024 10:35:09 +0100 Subject: [PATCH 20/22] update migrations to changes in dev --- ...3_alter_finding_options_finding_epss_percentile_and_more.py} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename dojo/db_migrations/{0201_alter_finding_options_finding_epss_percentile_and_more.py => 0203_alter_finding_options_finding_epss_percentile_and_more.py} (94%) diff --git a/dojo/db_migrations/0201_alter_finding_options_finding_epss_percentile_and_more.py b/dojo/db_migrations/0203_alter_finding_options_finding_epss_percentile_and_more.py similarity index 94% rename from dojo/db_migrations/0201_alter_finding_options_finding_epss_percentile_and_more.py rename to dojo/db_migrations/0203_alter_finding_options_finding_epss_percentile_and_more.py index abde0699aa9..00bf8b5e2ba 100644 --- a/dojo/db_migrations/0201_alter_finding_options_finding_epss_percentile_and_more.py +++ b/dojo/db_migrations/0203_alter_finding_options_finding_epss_percentile_and_more.py @@ -7,7 +7,7 @@ class Migration(migrations.Migration): dependencies = [ - ('dojo', '0200_finding_sla_expiration_date_product_async_updating_and_more'), + ('dojo', '0202_alter_dojo_group_social_provider'), ] operations = [ From 187309a5dbc529e7c75d81b3a37332f2a9ae071a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quirin=20Hardy=20Zie=C3=9Fler?= Date: Thu, 15 Feb 2024 10:37:53 +0100 Subject: [PATCH 21/22] merge dev into epss score * Update versions in application files * Update versions * Parse GitHub vulnerability version (#9462) * Fix SARIF parser with CodeQL rules (#9440) * fix for sarif parser with codeql rules * add check for extensions property * flake8 comparsion * finding sla expiration date field (part two) (#9494) * finding sla expiration date field (part two) * sla violation check updates * clean up of finding violates_sla property * flake8 fix * Update dojo/models.py Co-authored-by: Charles Neill <1749665+cneill@users.noreply.github.com> * Update 0201_populate_finding_sla_expiration_date.py --------- Co-authored-by: Charles Neill <1749665+cneill@users.noreply.github.com> * Jira Server/DataCenter: Update meta methods (#9512) * Jira Webhook: Catch comments from other issue updates (#9513) * Jira Webhook: Catch comments from other issue updates * Accommodate redirect responses * Update dojo/jira_link/views.py Co-authored-by: Charles Neill <1749665+cneill@users.noreply.github.com> * Fix syntax --------- Co-authored-by: Charles Neill <1749665+cneill@users.noreply.github.com> * add metrics page: "Product Tag Count" (fixes #9151) (#9152) * add metrics page: "Product Tag Count" It is fully based on "Product Type Count" metrics page. * fixup! add metrics page: "Product Tag Count" * Fix Flake8 * Update views.py --------- Co-authored-by: Cody Maffucci <46459665+Maffooch@users.noreply.github.com> * Release Drafter: Try validating inputs * Disallow duplicate tool types (#9530) * Disallow duplicate tool types * Fix Flake8 * Only validate on new creations * Force new name on tool type unit test * Engagement Surveys: Add missing leading slash (#9531) URL redirects were behaving strangely without this leading slash. it seems it was missed when all the others were added * Update versions in application files * Update versions in application files * Dojo_Group: Support for "RemoteUser" in model (#9405) * Use correct name references * fix db_mig * Update and rename 0201_alter_dojo_group_social_provider.py to 0202_alter_dojo_group_social_provider.py --------- Co-authored-by: Cody Maffucci <46459665+Maffooch@users.noreply.github.com> * Update rabbitmq:3.12.12-alpine Docker digest from 3.12.12 to 3.12.12-alpine (docker-compose.yml) (#9535) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * remove flot-axis library (#9540) * use full url for helm-repos and alias in renovate.json (#9525) With this change, renovate will create PRs to update the helm-dependencies, just as with docker-compose. Note that only setting the repository to the full URL did not work, I also had to add the registryAlias. * Update Helm release redis from 16.12.3 to ~16.13.0 (helm/defectdojo/Chart.yaml) (#9550) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update rabbitmq:3.12.12-alpine Docker digest from 3.12.12 to 3.12.12-alpine (docker-compose.yml) (#9541) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update postgres Docker tag from 16.1 to v16.2 (docker-compose.yml) (#9536) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update Helm release mysql from 9.1.8 to ~9.19.0 (helm/defectdojo/Chart.yaml) (#9545) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --------- Co-authored-by: DefectDojo release bot Co-authored-by: Cody Maffucci <46459665+Maffooch@users.noreply.github.com> Co-authored-by: Colm O hEigeartaigh Co-authored-by: Andrei Serebriakov Co-authored-by: Blake Owens <76979297+blakeaowens@users.noreply.github.com> Co-authored-by: Charles Neill <1749665+cneill@users.noreply.github.com> Co-authored-by: tomaszn Co-authored-by: kiblik Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Felix Hernandez Co-authored-by: Sebastian Gumprich --- .github/renovate.json | 5 +- .github/workflows/fetch-oas.yml | 13 +- components/package.json | 1 - docker-compose.yml | 4 +- docs/content/en/usage/features.md | 3 + dojo/api_v2/serializers.py | 8 + ...01_populate_finding_sla_expiration_date.py | 133 ++++++++++++ .../0202_alter_dojo_group_social_provider.py | 18 ++ dojo/filters.py | 17 +- dojo/forms.py | 37 +++- dojo/jira_link/helper.py | 12 +- dojo/jira_link/views.py | 203 ++++++++++-------- dojo/locale/en/LC_MESSAGES/django.po | 4 + dojo/metrics/urls.py | 2 + dojo/metrics/views.py | 164 +++++++++++++- dojo/models.py | 45 ++-- dojo/pipeline.py | 2 +- dojo/remote_user.py | 3 +- dojo/templates/base.html | 5 + dojo/templates/dojo/dashboard-metrics.html | 1 - dojo/templates/dojo/dashboard.html | 2 +- dojo/templates/dojo/endpoint_pdf_report.html | 1 - .../templates/dojo/engagement_pdf_report.html | 1 - dojo/templates/dojo/finding_pdf_report.html | 1 - .../dojo/product_endpoint_pdf_report.html | 1 - dojo/templates/dojo/product_metrics.html | 1 - dojo/templates/dojo/product_pdf_report.html | 1 - .../dojo/product_type_pdf_report.html | 1 - dojo/templates/dojo/pt_counts.html | 10 +- dojo/templates/dojo/test_pdf_report.html | 1 - dojo/templates/dojo/view_endpoint.html | 1 - dojo/tools/github_vulnerability/parser.py | 3 + dojo/tools/sarif/parser.py | 5 +- dojo/utils.py | 10 +- helm/defectdojo/Chart.lock | 8 +- helm/defectdojo/Chart.yaml | 16 +- .../github-vuln-version.json | 106 +++++++++ unittests/test_remote_user.py | 4 +- unittests/test_swagger_schema.py | 3 + .../tools/test_github_vulnerability_parser.py | 15 ++ 40 files changed, 693 insertions(+), 178 deletions(-) create mode 100644 dojo/db_migrations/0201_populate_finding_sla_expiration_date.py create mode 100644 dojo/db_migrations/0202_alter_dojo_group_social_provider.py create mode 100644 unittests/scans/github_vulnerability/github-vuln-version.json diff --git a/.github/renovate.json b/.github/renovate.json index f64560dd545..7c9c6623cd6 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -12,5 +12,8 @@ "commitMessageExtra": "from {{currentVersion}} to {{#if isMajor}}v{{{newMajor}}}{{else}}{{#if isSingleVersion}}v{{{toVersion}}}{{else}}{{{newValue}}}{{/if}}{{/if}}", "commitMessageSuffix": "({{packageFile}})", "labels": ["dependencies"] - }] + }], + "registryAliases": { + "bitnami": "https://charts.bitnami.com/bitnami" + } } diff --git a/.github/workflows/fetch-oas.yml b/.github/workflows/fetch-oas.yml index 44692ddb5cb..0dd32805b58 100644 --- a/.github/workflows/fetch-oas.yml +++ b/.github/workflows/fetch-oas.yml @@ -10,6 +10,9 @@ on: This will override any version calculated by the release-drafter. required: true +env: + release_version: ${{ github.event.inputs.version || github.event.inputs.release_number }} + jobs: oas_fetch: name: Fetch OpenAPI Specifications @@ -21,19 +24,19 @@ jobs: - name: Checkout uses: actions/checkout@v4 with: - ref: ${{ github.event.inputs.version }} + ref: release/${{ env.release_version }} - name: Load docker images run: |- - docker pull defectdojo/defectdojo-django:${{ github.event.inputs.version }}-alpine - docker pull defectdojo/defectdojo-nginx:${{ github.event.inputs.version }}-alpine + docker pull defectdojo/defectdojo-django:${{ env.release_version }}-alpine + docker pull defectdojo/defectdojo-nginx:${{ env.release_version }}-alpine docker images - name: Start Dojo run: docker-compose --profile postgres-redis --env-file ./docker/environments/postgres-redis.env up --no-deps -d postgres nginx uwsgi env: - DJANGO_VERSION: ${{ github.event.inputs.version }}-alpine - NGINX_VERSION: ${{ github.event.inputs.version }}-alpine + DJANGO_VERSION: ${{ env.release_version }}-alpine + NGINX_VERSION: ${{ env.release_version }}-alpine - name: Download OpenAPI Specifications run: |- diff --git a/components/package.json b/components/package.json index 9a57f7b78dd..b4e7dc9db26 100644 --- a/components/package.json +++ b/components/package.json @@ -21,7 +21,6 @@ "drmonty-datatables-responsive": "^1.0.0", "easymde": "^2.18.0", "flot": "flot/flot#~0.8.3", - "flot-axis": "markrcote/flot-axislabels#*", "font-awesome": "^4.0.0", "fullcalendar": "^3.10.2", "google-code-prettify": "^1.0.0", diff --git a/docker-compose.yml b/docker-compose.yml index 4394261baf4..1b43001c3c1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -138,7 +138,7 @@ services: volumes: - defectdojo_data:/var/lib/mysql postgres: - image: postgres:16.1-alpine@sha256:17eb369d9330fe7fbdb2f705418c18823d66322584c77c2b43cc0e1851d01de7 + image: postgres:16.2-alpine@sha256:bbd7346fab25b7e0b25f214829d6ebfb78ef0465059492e46dee740ce8fcd844 profiles: - postgres-rabbitmq - postgres-redis @@ -149,7 +149,7 @@ services: volumes: - defectdojo_postgres:/var/lib/postgresql/data rabbitmq: - image: rabbitmq:3.12.12-alpine@sha256:fcd6a66524be55c15c81011dc87cc4b6e4405130fbb950c21ad1d31e8f6322dd + image: rabbitmq:3.12.12-alpine@sha256:9144c0eca261e36ffd1a3f9ef21a860242a4a60e0211bbade82c80910958a5e9 profiles: - mysql-rabbitmq - postgres-rabbitmq diff --git a/docs/content/en/usage/features.md b/docs/content/en/usage/features.md index fdd3e19480d..470c009bf71 100644 --- a/docs/content/en/usage/features.md +++ b/docs/content/en/usage/features.md @@ -557,6 +557,9 @@ Product Type Counts ![Product Type Counts](../../images/met_2.png) +Product Tag Counts +: Same as above, but for a group of products sharing a tag. + Simple Metrics : Provides tabular data for all Product Types. The data displayed in this view is the total number of S0, S1, S2, S3, S4, Opened This diff --git a/dojo/api_v2/serializers.py b/dojo/api_v2/serializers.py index ff21d50aff7..5778f2147ca 100644 --- a/dojo/api_v2/serializers.py +++ b/dojo/api_v2/serializers.py @@ -1133,6 +1133,14 @@ class Meta: model = Tool_Type fields = "__all__" + def validate(self, data): + if self.context["request"].method == "POST": + name = data.get("name") + # Make sure this will not create a duplicate test type + if Tool_Type.objects.filter(name=name).count() > 0: + raise serializers.ValidationError('A Tool Type with the name already exists') + return data + class RegulationSerializer(serializers.ModelSerializer): class Meta: diff --git a/dojo/db_migrations/0201_populate_finding_sla_expiration_date.py b/dojo/db_migrations/0201_populate_finding_sla_expiration_date.py new file mode 100644 index 00000000000..4b886301de7 --- /dev/null +++ b/dojo/db_migrations/0201_populate_finding_sla_expiration_date.py @@ -0,0 +1,133 @@ +from django.db import migrations +from django.utils import timezone +from datetime import datetime +from django.conf import settings +from dateutil.relativedelta import relativedelta +import logging + +from dojo.utils import get_work_days + +logger = logging.getLogger(__name__) + + +def calculate_sla_expiration_dates(apps, schema_editor): + System_Settings = apps.get_model('dojo', 'System_Settings') + + ss, _ = System_Settings.objects.get_or_create() + if not ss.enable_finding_sla: + return + + logger.info('Calculating SLA expiration dates for all findings') + + SLA_Configuration = apps.get_model('dojo', 'SLA_Configuration') + Finding = apps.get_model('dojo', 'Finding') + + findings = Finding.objects.filter(sla_expiration_date__isnull=True).order_by('id').only('id', 'sla_start_date', 'date', 'severity', 'test', 'mitigated') + + page_size = 1000 + total_count = Finding.objects.filter(id__gt=0).count() + logger.info('Found %d findings to be updated', total_count) + + i = 0 + batch = [] + last_id = 0 + total_pages = (total_count // page_size) + 2 + for p in range(1, total_pages): + page = findings.filter(id__gt=last_id)[:page_size] + for find in page: + i += 1 + last_id = find.id + + start_date = find.sla_start_date if find.sla_start_date else find.date + + sla_config = SLA_Configuration.objects.filter(id=find.test.engagement.product.sla_configuration_id).first() + sla_period = getattr(sla_config, find.severity.lower(), None) + + days = None + if settings.SLA_BUSINESS_DAYS: + if find.mitigated: + days = get_work_days(find.date, find.mitigated.date()) + else: + days = get_work_days(find.date, timezone.now().date()) + else: + if isinstance(start_date, datetime): + start_date = start_date.date() + + if find.mitigated: + days = (find.mitigated.date() - start_date).days + else: + days = (timezone.now().date() - start_date).days + + days = days if days > 0 else 0 + + days_remaining = None + if sla_period: + days_remaining = sla_period - days + + if days_remaining: + if find.mitigated: + find.sla_expiration_date = find.mitigated.date() + relativedelta(days=days_remaining) + else: + find.sla_expiration_date = timezone.now().date() + relativedelta(days=days_remaining) + + batch.append(find) + + if (i > 0 and i % page_size == 0): + Finding.objects.bulk_update(batch, ['sla_expiration_date']) + batch = [] + logger.info('%s out of %s findings processed...', i, total_count) + + Finding.objects.bulk_update(batch, ['sla_expiration_date']) + batch = [] + logger.info('%s out of %s findings processed...', i, total_count) + + +def reset_sla_expiration_dates(apps, schema_editor): + System_Settings = apps.get_model('dojo', 'System_Settings') + + ss, _ = System_Settings.objects.get_or_create() + if not ss.enable_finding_sla: + return + + logger.info('Resetting SLA expiration dates for all findings') + + Finding = apps.get_model('dojo', 'Finding') + + findings = Finding.objects.filter(sla_expiration_date__isnull=False).order_by('id').only('id') + + page_size = 1000 + total_count = Finding.objects.filter(id__gt=0).count() + logger.info('Found %d findings to be reset', total_count) + + i = 0 + batch = [] + last_id = 0 + total_pages = (total_count // page_size) + 2 + for p in range(1, total_pages): + page = findings.filter(id__gt=last_id)[:page_size] + for find in page: + i += 1 + last_id = find.id + + find.sla_expiration_date = None + batch.append(find) + + if (i > 0 and i % page_size == 0): + Finding.objects.bulk_update(batch, ['sla_expiration_date']) + batch = [] + logger.info('%s out of %s findings processed...', i, total_count) + + Finding.objects.bulk_update(batch, ['sla_expiration_date']) + batch = [] + logger.info('%s out of %s findings processed...', i, total_count) + + +class Migration(migrations.Migration): + + dependencies = [ + ('dojo', '0200_finding_sla_expiration_date_product_async_updating_and_more'), + ] + + operations = [ + migrations.RunPython(calculate_sla_expiration_dates, reset_sla_expiration_dates), + ] diff --git a/dojo/db_migrations/0202_alter_dojo_group_social_provider.py b/dojo/db_migrations/0202_alter_dojo_group_social_provider.py new file mode 100644 index 00000000000..9bbc7e2e5c6 --- /dev/null +++ b/dojo/db_migrations/0202_alter_dojo_group_social_provider.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1.13 on 2024-01-25 00:07 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dojo', '0201_populate_finding_sla_expiration_date'), + ] + + operations = [ + migrations.AlterField( + model_name='dojo_group', + name='social_provider', + field=models.CharField(blank=True, choices=[('AzureAD', 'AzureAD'), ('Remote', 'Remote')], help_text='Group imported from a social provider.', max_length=10, null=True, verbose_name='Social Authentication Provider'), + ), + ] diff --git a/dojo/filters.py b/dojo/filters.py index 267d1d4b23d..2cdfe7a57af 100644 --- a/dojo/filters.py +++ b/dojo/filters.py @@ -11,6 +11,7 @@ from django.conf import settings import six from django.utils.translation import gettext_lazy as _ +from django.utils import timezone from django_filters import FilterSet, CharFilter, OrderingFilter, \ ModelMultipleChoiceFilter, ModelChoiceFilter, MultipleChoiceFilter, \ BooleanFilter, NumberFilter, DateFilter @@ -148,16 +149,12 @@ def any(self, qs, name): return qs def sla_satisfied(self, qs, name): - for finding in qs: - if finding.violates_sla: - qs = qs.exclude(id=finding.id) - return qs + # return findings that have an sla expiration date after today or no sla expiration date + return qs.filter(Q(sla_expiration_date__isnull=True) | Q(sla_expiration_date__gt=timezone.now().date())) def sla_violated(self, qs, name): - for finding in qs: - if not finding.violates_sla: - qs = qs.exclude(id=finding.id) - return qs + # return active findings that have an sla expiration date before today + return qs.filter(Q(active=True) & Q(sla_expiration_date__lt=timezone.now().date())) options = { None: (_('Any'), any), @@ -184,13 +181,13 @@ def any(self, qs, name): def sla_satisifed(self, qs, name): for product in qs: - if product.violates_sla: + if product.violates_sla(): qs = qs.exclude(id=product.id) return qs def sla_violated(self, qs, name): for product in qs: - if not product.violates_sla: + if not product.violates_sla(): qs = qs.exclude(id=product.id) return qs diff --git a/dojo/forms.py b/dojo/forms.py index 558c09ae69d..d831bb7132d 100755 --- a/dojo/forms.py +++ b/dojo/forms.py @@ -2119,21 +2119,37 @@ def get_years(): return [(now.year, now.year), (now.year - 1, now.year - 1), (now.year - 2, now.year - 2)] -class ProductTypeCountsForm(forms.Form): +class ProductCountsFormBase(forms.Form): month = forms.ChoiceField(choices=list(MONTHS.items()), required=True, error_messages={ 'required': '*'}) year = forms.ChoiceField(choices=get_years, required=True, error_messages={ 'required': '*'}) + + +class ProductTypeCountsForm(ProductCountsFormBase): product_type = forms.ModelChoiceField(required=True, queryset=Product_Type.objects.none(), error_messages={ 'required': '*'}) def __init__(self, *args, **kwargs): - super(ProductTypeCountsForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.fields['product_type'].queryset = get_authorized_product_types(Permissions.Product_Type_View) +class ProductTagCountsForm(ProductCountsFormBase): + product_tag = forms.ModelChoiceField(required=True, + queryset=Product.tags.tag_model.objects.none().order_by('name'), + error_messages={ + 'required': '*'}) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + prods = get_authorized_products(Permissions.Product_View) + tags_available_to_user = Product.tags.tag_model.objects.filter(product__in=prods) + self.fields['product_tag'].queryset = tags_available_to_user + + class APIKeyForm(forms.ModelForm): id = forms.IntegerField(required=True, widget=forms.widgets.HiddenInput()) @@ -2388,6 +2404,23 @@ class Meta: model = Tool_Type exclude = ['product'] + def __init__(self, *args, **kwargs): + instance = kwargs.get('instance', None) + self.newly_created = True + if instance is not None: + self.newly_created = instance.pk is None + super().__init__(*args, **kwargs) + + def clean(self): + form_data = self.cleaned_data + if self.newly_created: + name = form_data.get("name") + # Make sure this will not create a duplicate test type + if Tool_Type.objects.filter(name=name).count() > 0: + raise forms.ValidationError('A Tool Type with the name already exists') + + return form_data + class RegulationForm(forms.ModelForm): class Meta: diff --git a/dojo/jira_link/helper.py b/dojo/jira_link/helper.py index 8a8b208d45f..ecd5da084f8 100644 --- a/dojo/jira_link/helper.py +++ b/dojo/jira_link/helper.py @@ -1036,28 +1036,28 @@ def get_issuetype_fields( else: try: - issuetypes = jira.createmeta_issuetypes(project_key) + issuetypes = jira.project_issue_types(project_key) except JIRAError as e: e.text = f"Jira API call 'createmeta/issuetypes' failed with status: {e.status_code} and message: {e.text}. Project misconfigured or no permissions in Jira ?" raise e issuetype_id = None - for it in issuetypes['values']: - if it['name'] == issuetype_name: - issuetype_id = it['id'] + for it in issuetypes: + if it.name == issuetype_name: + issuetype_id = it.id break if not issuetype_id: raise JIRAError("Issue type ID can not be matched. Misconfigured default issue type ?") try: - issuetype_fields = jira.createmeta_fieldtypes(project_key, issuetype_id) + issuetype_fields = jira.project_issue_fields(project_key, issuetype_id) except JIRAError as e: e.text = f"Jira API call 'createmeta/fieldtypes' failed with status: {e.status_code} and message: {e.text}. Misconfigured project or default issue type ?" raise e try: - issuetype_fields = [f['fieldId'] for f in issuetype_fields['values']] + issuetype_fields = [f.fieldId for f in issuetype_fields] except Exception: raise JIRAError("Misconfigured default issue type ?") diff --git a/dojo/jira_link/views.py b/dojo/jira_link/views.py index e05ea5ce219..a1a73f0b015 100644 --- a/dojo/jira_link/views.py +++ b/dojo/jira_link/views.py @@ -1,7 +1,7 @@ # Standard library imports import json import logging - +import datetime # Third party imports from django.contrib import messages from django.contrib.admin.utils import NestedObjects @@ -105,97 +105,13 @@ def webhook(request, secret=None): if findings: for finding in findings: jira_helper.process_resolution_from_jira(finding, resolution_id, resolution_name, assignee_name, jira_now, jissue) + # Check for any comment that could have come along with the resolution + if (error_response := check_for_and_create_comment(parsed)) is not None: + return error_response if parsed.get('webhookEvent') == 'comment_created': - """ - example incoming requests from JIRA Server 8.14.0 - { - "timestamp":1610269967824, - "webhookEvent":"comment_created", - "comment":{ - "self":"https://jira.host.com/rest/api/2/issue/115254/comment/466578", - "id":"466578", - "author":{ - "self":"https://jira.host.com/rest/api/2/user?username=defect.dojo", - "name":"defect.dojo", - "key":"defect.dojo", # seems to be only present on JIRA Server, not on Cloud - "avatarUrls":{ - "48x48":"https://www.gravatar.com/avatar/9637bfb970eff6176357df615f548f1c?d=mm&s=48", - "24x24":"https://www.gravatar.com/avatar/9637bfb970eff6176357df615f548f1c?d=mm&s=24", - "16x16":"https://www.gravatar.com/avatar9637bfb970eff6176357df615f548f1c?d=mm&s=16", - "32x32":"https://www.gravatar.com/avatar/9637bfb970eff6176357df615f548f1c?d=mm&s=32" - }, - "displayName":"Defect Dojo", - "active":true, - "timeZone":"Europe/Amsterdam" - }, - "body":"(Valentijn Scholten):test4", - "updateAuthor":{ - "self":"https://jira.host.com/rest/api/2/user?username=defect.dojo", - "name":"defect.dojo", - "key":"defect.dojo", - "avatarUrls":{ - "48x48":"https://www.gravatar.com/avatar/9637bfb970eff6176357df615f548f1c?d=mm&s=48", - "24x24""https://www.gravatar.com/avatar/9637bfb970eff6176357df615f548f1c?d=mm&s=24", - "16x16":"https://www.gravatar.com/avatar/9637bfb970eff6176357df615f548f1c?d=mm&s=16", - "32x32":"https://www.gravatar.com/avatar/9637bfb970eff6176357df615f548f1c?d=mm&s=32" - }, - "displayName":"Defect Dojo", - "active":true, - "timeZone":"Europe/Amsterdam" - }, - "created":"2021-01-10T10:12:47.824+0100", - "updated":"2021-01-10T10:12:47.824+0100" - } - } - """ - - comment_text = parsed['comment']['body'] - commentor = '' - if 'name' in parsed['comment']['updateAuthor']: - commentor = parsed['comment']['updateAuthor']['name'] - elif 'emailAddress' in parsed['comment']['updateAuthor']: - commentor = parsed['comment']['updateAuthor']['emailAddress'] - else: - logger.debug('Could not find the author of this jira comment!') - commentor_display_name = parsed['comment']['updateAuthor']['displayName'] - # example: body['comment']['self'] = "http://www.testjira.com/jira_under_a_path/rest/api/2/issue/666/comment/456843" - jid = parsed['comment']['self'].split('/')[-3] - jissue = get_object_or_404(JIRA_Issue, jira_id=jid) - logging.info(f"Received issue comment for {jissue.jira_key}") - logger.debug('jissue: %s', vars(jissue)) - - jira_usernames = JIRA_Instance.objects.values_list('username', flat=True) - for jira_userid in jira_usernames: - # logger.debug('incoming username: %s jira config username: %s', commentor.lower(), jira_userid.lower()) - if jira_userid.lower() == commentor.lower(): - logger.debug('skipping incoming JIRA comment as the user id of the comment in JIRA (%s) matches the JIRA username in DefectDojo (%s)', commentor.lower(), jira_userid.lower()) - return HttpResponse('') - - findings = None - if jissue.finding: - findings = [jissue.finding] - create_notification(event='other', title=f'JIRA incoming comment - {jissue.finding}', finding=jissue.finding, url=reverse("view_finding", args=(jissue.finding.id,)), icon='check') - - elif jissue.finding_group: - findings = [jissue.finding_group.findings.all()] - create_notification(event='other', title=f'JIRA incoming comment - {jissue.finding}', finding=jissue.finding, url=reverse("view_finding_group", args=(jissue.finding_group.id,)), icon='check') - - elif jissue.engagement: - return HttpResponse('Comment for engagement ignored') - else: - raise Http404(f'No finding or engagement found for JIRA issue {jissue.jira_key}') - - for finding in findings: - # logger.debug('finding: %s', vars(jissue.finding)) - new_note = Notes() - new_note.entry = f'({commentor_display_name} ({commentor})): {comment_text}' - new_note.author, created = User.objects.get_or_create(username='JIRA') - new_note.save() - finding.notes.add(new_note) - finding.jira_issue.jira_change = timezone.now() - finding.jira_issue.save() - finding.save() + if (error_response := check_for_and_create_comment(parsed)) is not None: + return error_response if parsed.get('webhookEvent') not in ['comment_created', 'jira:issue_updated']: logger.info(f"Unrecognized JIRA webhook event received: {parsed.get('webhookEvent')}") @@ -203,6 +119,7 @@ def webhook(request, secret=None): except Exception as e: if isinstance(e, Http404): logger.warning('404 error processing JIRA webhook') + logger.warning(str(e)) else: logger.exception(e) @@ -218,6 +135,112 @@ def webhook(request, secret=None): return HttpResponse('') +def check_for_and_create_comment(parsed_json): + """ + example incoming requests from JIRA Server 8.14.0 + { + "timestamp":1610269967824, + "webhookEvent":"comment_created", + "comment":{ + "self":"https://jira.host.com/rest/api/2/issue/115254/comment/466578", + "id":"466578", + "author":{ + "self":"https://jira.host.com/rest/api/2/user?username=defect.dojo", + "name":"defect.dojo", + "key":"defect.dojo", # seems to be only present on JIRA Server, not on Cloud + "avatarUrls":{ + "48x48":"https://www.gravatar.com/avatar/9637bfb970eff6176357df615f548f1c?d=mm&s=48", + "24x24":"https://www.gravatar.com/avatar/9637bfb970eff6176357df615f548f1c?d=mm&s=24", + "16x16":"https://www.gravatar.com/avatar9637bfb970eff6176357df615f548f1c?d=mm&s=16", + "32x32":"https://www.gravatar.com/avatar/9637bfb970eff6176357df615f548f1c?d=mm&s=32" + }, + "displayName":"Defect Dojo", + "active":true, + "timeZone":"Europe/Amsterdam" + }, + "body":"(Valentijn Scholten):test4", + "updateAuthor":{ + "self":"https://jira.host.com/rest/api/2/user?username=defect.dojo", + "name":"defect.dojo", + "key":"defect.dojo", + "avatarUrls":{ + "48x48":"https://www.gravatar.com/avatar/9637bfb970eff6176357df615f548f1c?d=mm&s=48", + "24x24""https://www.gravatar.com/avatar/9637bfb970eff6176357df615f548f1c?d=mm&s=24", + "16x16":"https://www.gravatar.com/avatar/9637bfb970eff6176357df615f548f1c?d=mm&s=16", + "32x32":"https://www.gravatar.com/avatar/9637bfb970eff6176357df615f548f1c?d=mm&s=32" + }, + "displayName":"Defect Dojo", + "active":true, + "timeZone":"Europe/Amsterdam" + }, + "created":"2021-01-10T10:12:47.824+0100", + "updated":"2021-01-10T10:12:47.824+0100" + } + } + """ + comment = parsed_json.get("comment", None) + if comment is None: + return + + comment_text = comment.get('body') + commenter = '' + if 'name' in comment.get('updateAuthor'): + commenter = comment.get('updateAuthor', {}).get('name') + elif 'emailAddress' in comment.get('updateAuthor'): + commenter = comment.get('updateAuthor', {}).get('emailAddress') + else: + logger.debug('Could not find the author of this jira comment!') + commenter_display_name = comment.get('updateAuthor', {}).get('displayName') + # example: body['comment']['self'] = "http://www.testjira.com/jira_under_a_path/rest/api/2/issue/666/comment/456843" + jid = comment.get('self', '').split('/')[-3] + jissue = get_object_or_404(JIRA_Issue, jira_id=jid) + logging.info(f"Received issue comment for {jissue.jira_key}") + logger.debug('jissue: %s', vars(jissue)) + + jira_usernames = JIRA_Instance.objects.values_list('username', flat=True) + for jira_user_id in jira_usernames: + # logger.debug('incoming username: %s jira config username: %s', commenter.lower(), jira_user_id.lower()) + if jira_user_id.lower() == commenter.lower(): + logger.debug('skipping incoming JIRA comment as the user id of the comment in JIRA (%s) matches the JIRA username in DefectDojo (%s)', commenter.lower(), jira_user_id.lower()) + return HttpResponse('') + + findings = None + if jissue.finding: + findings = [jissue.finding] + create_notification(event='other', title=f'JIRA incoming comment - {jissue.finding}', finding=jissue.finding, url=reverse("view_finding", args=(jissue.finding.id,)), icon='check') + + elif jissue.finding_group: + findings = [jissue.finding_group.findings.all()] + create_notification(event='other', title=f'JIRA incoming comment - {jissue.finding}', finding=jissue.finding, url=reverse("view_finding_group", args=(jissue.finding_group.id,)), icon='check') + + elif jissue.engagement: + return HttpResponse('Comment for engagement ignored') + else: + raise Http404(f'No finding or engagement found for JIRA issue {jissue.jira_key}') + + # Set the fields for the notes + author, _ = User.objects.get_or_create(username='JIRA') + entry = f'({commenter_display_name} ({commenter})): {comment_text}' + # Iterate (potentially) over each of the findings the note should be added to + for finding in findings: + # Determine if this exact note was created within the last 30 seconds to avoid duplicate notes + existing_notes = finding.notes.filter( + entry=entry, + author=author, + date__gte=(timezone.now() - datetime.timedelta(seconds=30)), + ) + # Check the query for any hits + if existing_notes.count() == 0: + new_note = Notes() + new_note.entry = entry + new_note.author = author + new_note.save() + finding.notes.add(new_note) + finding.jira_issue.jira_change = timezone.now() + finding.jira_issue.save() + finding.save() + + def get_custom_field(jira, label): url = jira._options["server"].strip('/') + '/rest/api/2/field' response = jira._session.get(url).json() diff --git a/dojo/locale/en/LC_MESSAGES/django.po b/dojo/locale/en/LC_MESSAGES/django.po index dbb9e756559..ab26c8cbdb4 100644 --- a/dojo/locale/en/LC_MESSAGES/django.po +++ b/dojo/locale/en/LC_MESSAGES/django.po @@ -2692,6 +2692,10 @@ msgstr "" msgid "Product Type Counts" msgstr "" +#: dojo/templates/base.html +msgid "Product Tag Counts" +msgstr "" + #: dojo/templates/base.html msgid "Users" msgstr "" diff --git a/dojo/metrics/urls.py b/dojo/metrics/urls.py index 06b0726a56a..7b2683cf6f7 100644 --- a/dojo/metrics/urls.py +++ b/dojo/metrics/urls.py @@ -18,6 +18,8 @@ views.metrics, name='product_type_metrics'), re_path(r'^metrics/product/type/counts$', views.product_type_counts, name='product_type_counts'), + re_path(r'^metrics/product/tag/counts$', + views.product_tag_counts, name='product_tag_counts'), re_path(r'^metrics/engineer$', views.engineer_metrics, name='engineer_metrics'), re_path(r'^metrics/engineer/(?P\d+)$', views.view_engineer, diff --git a/dojo/metrics/views.py b/dojo/metrics/views.py index e00cbcb857a..4d9236fb58a 100644 --- a/dojo/metrics/views.py +++ b/dojo/metrics/views.py @@ -21,7 +21,7 @@ from django.utils import timezone from dojo.filters import MetricsFindingFilter, UserFilter, MetricsEndpointFilter -from dojo.forms import SimpleMetricsForm, ProductTypeCountsForm +from dojo.forms import SimpleMetricsForm, ProductTypeCountsForm, ProductTagCountsForm from dojo.models import Product_Type, Finding, Product, Engagement, Test, \ Risk_Acceptance, Dojo_User, Endpoint_Status from dojo.utils import get_page_items, add_breadcrumb, findings_this_period, opened_in_period, count_findings, \ @@ -586,13 +586,13 @@ def product_type_counts(request): end_date.month, end_date.day, tzinfo=timezone.get_current_timezone()) - oip = opened_in_period(start_date, end_date, pt) + oip = opened_in_period(start_date, end_date, test__engagement__product__prod_type=pt) # trending data - 12 months for x in range(12, 0, -1): opened_in_period_list.append( opened_in_period(start_date + relativedelta(months=-x), end_of_month + relativedelta(months=-x), - pt)) + test__engagement__product__prod_type=pt)) opened_in_period_list.append(oip) @@ -697,6 +697,164 @@ def product_type_counts(request): ) +def product_tag_counts(request): + form = ProductTagCountsForm() + opened_in_period_list = [] + oip = None + cip = None + aip = None + all_current_in_pt = None + top_ten = None + pt = None + today = timezone.now() + first_of_month = today.replace(day=1, hour=0, minute=0, second=0, microsecond=0) + mid_month = first_of_month.replace(day=15, hour=23, minute=59, second=59, microsecond=999999) + end_of_month = mid_month.replace(day=monthrange(today.year, today.month)[1], hour=23, minute=59, second=59, + microsecond=999999) + start_date = first_of_month + end_date = end_of_month + + if request.method == 'GET' and 'month' in request.GET and 'year' in request.GET and 'product_tag' in request.GET: + form = ProductTagCountsForm(request.GET) + if form.is_valid(): + prods = get_authorized_products(Permissions.Product_View) + + pt = form.cleaned_data['product_tag'] + month = int(form.cleaned_data['month']) + year = int(form.cleaned_data['year']) + first_of_month = first_of_month.replace(month=month, year=year) + + month_requested = datetime(year, month, 1) + + end_of_month = month_requested.replace(day=monthrange(month_requested.year, month_requested.month)[1], + hour=23, minute=59, second=59, microsecond=999999) + start_date = first_of_month + start_date = datetime(start_date.year, + start_date.month, start_date.day, + tzinfo=timezone.get_current_timezone()) + end_date = end_of_month + end_date = datetime(end_date.year, + end_date.month, end_date.day, + tzinfo=timezone.get_current_timezone()) + + oip = opened_in_period(start_date, end_date, + test__engagement__product__tags__name=pt, + test__engagement__product__in=prods) + + # trending data - 12 months + for x in range(12, 0, -1): + opened_in_period_list.append( + opened_in_period(start_date + relativedelta(months=-x), end_of_month + relativedelta(months=-x), + test__engagement__product__tags__name=pt, test__engagement__product__in=prods)) + + opened_in_period_list.append(oip) + + closed_in_period = Finding.objects.filter(mitigated__date__range=[start_date, end_date], + test__engagement__product__tags__name=pt, + test__engagement__product__in=prods, + severity__in=('Critical', 'High', 'Medium', 'Low')).values( + 'numerical_severity').annotate(Count('numerical_severity')).order_by('numerical_severity') + + total_closed_in_period = Finding.objects.filter(mitigated__date__range=[start_date, end_date], + test__engagement__product__tags__name=pt, + test__engagement__product__in=prods, + severity__in=( + 'Critical', 'High', 'Medium', 'Low')).aggregate( + total=Sum( + Case(When(severity__in=('Critical', 'High', 'Medium', 'Low'), + then=Value(1)), + output_field=IntegerField())))['total'] + + overall_in_pt = Finding.objects.filter(date__lt=end_date, + verified=True, + false_p=False, + duplicate=False, + out_of_scope=False, + mitigated__isnull=True, + test__engagement__product__tags__name=pt, + test__engagement__product__in=prods, + severity__in=('Critical', 'High', 'Medium', 'Low')).values( + 'numerical_severity').annotate(Count('numerical_severity')).order_by('numerical_severity') + + total_overall_in_pt = Finding.objects.filter(date__lte=end_date, + verified=True, + false_p=False, + duplicate=False, + out_of_scope=False, + mitigated__isnull=True, + test__engagement__product__tags__name=pt, + test__engagement__product__in=prods, + severity__in=('Critical', 'High', 'Medium', 'Low')).aggregate( + total=Sum( + Case(When(severity__in=('Critical', 'High', 'Medium', 'Low'), + then=Value(1)), + output_field=IntegerField())))['total'] + + all_current_in_pt = Finding.objects.filter(date__lte=end_date, + verified=True, + false_p=False, + duplicate=False, + out_of_scope=False, + mitigated__isnull=True, + test__engagement__product__tags__name=pt, + test__engagement__product__in=prods, + severity__in=( + 'Critical', 'High', 'Medium', 'Low')).prefetch_related( + 'test__engagement__product', + 'test__engagement__product__prod_type', + 'test__engagement__risk_acceptance', + 'reporter').order_by( + 'numerical_severity') + + top_ten = Product.objects.filter(engagement__test__finding__date__lte=end_date, + engagement__test__finding__verified=True, + engagement__test__finding__false_p=False, + engagement__test__finding__duplicate=False, + engagement__test__finding__out_of_scope=False, + engagement__test__finding__mitigated__isnull=True, + engagement__test__finding__severity__in=( + 'Critical', 'High', 'Medium', 'Low'), + tags__name=pt, engagement__product__in=prods) + top_ten = severity_count(top_ten, 'annotate', 'engagement__test__finding__severity').order_by('-critical', '-high', '-medium', '-low')[:10] + + cip = {'S0': 0, + 'S1': 0, + 'S2': 0, + 'S3': 0, + 'Total': total_closed_in_period} + + aip = {'S0': 0, + 'S1': 0, + 'S2': 0, + 'S3': 0, + 'Total': total_overall_in_pt} + + for o in closed_in_period: + cip[o['numerical_severity']] = o['numerical_severity__count'] + + for o in overall_in_pt: + aip[o['numerical_severity']] = o['numerical_severity__count'] + else: + messages.add_message(request, messages.ERROR, _("Please choose month and year and the Product Tag."), + extra_tags='alert-danger') + + add_breadcrumb(title=_("Bi-Weekly Metrics"), top_level=True, request=request) + + return render(request, + 'dojo/pt_counts.html', + {'form': form, + 'start_date': start_date, + 'end_date': end_date, + 'opened_in_period': oip, + 'trending_opened': opened_in_period_list, + 'closed_in_period': cip, + 'overall_in_pt': aip, + 'all_current_in_pt': all_current_in_pt, + 'top_ten': top_ten, + 'pt': pt} + ) + + def engineer_metrics(request): # only superusers can select other users to view if request.user.is_superuser: diff --git a/dojo/models.py b/dojo/models.py index 2b6beb27b49..0ce85d4e3d3 100755 --- a/dojo/models.py +++ b/dojo/models.py @@ -246,14 +246,16 @@ class UserContactInfo(models.Model): class Dojo_Group(models.Model): AZURE = 'AzureAD' + REMOTE = 'Remote' SOCIAL_CHOICES = ( (AZURE, _('AzureAD')), + (REMOTE, _('Remote')), ) name = models.CharField(max_length=255, unique=True) description = models.CharField(max_length=4000, null=True, blank=True) users = models.ManyToManyField(Dojo_User, through='Dojo_Group_Member', related_name='users', blank=True) auth_group = models.ForeignKey(Group, null=True, blank=True, on_delete=models.CASCADE) - social_provider = models.CharField(max_length=10, choices=SOCIAL_CHOICES, blank=True, null=True, help_text='Group imported from a social provider.', verbose_name='Social Authentication Provider') + social_provider = models.CharField(max_length=10, choices=SOCIAL_CHOICES, blank=True, null=True, help_text=_('Group imported from a social provider.'), verbose_name=_('Social Authentication Provider')) def __str__(self): return self.name @@ -1102,7 +1104,7 @@ def findings_active_verified_count(self): @cached_property def endpoint_host_count(self): # active_endpoints is (should be) prefetched - endpoints = self.active_endpoints + endpoints = getattr(self, 'active_endpoints', None) hosts = [] for e in endpoints: @@ -1116,7 +1118,10 @@ def endpoint_host_count(self): @cached_property def endpoint_count(self): # active_endpoints is (should be) prefetched - return len(self.active_endpoints) + endpoints = getattr(self, 'active_endpoints', None) + if endpoints: + return len(self.active_endpoints) + return None def open_findings(self, start_date=None, end_date=None): if start_date is None or end_date is None: @@ -1192,13 +1197,11 @@ def get_absolute_url(self): from django.urls import reverse return reverse('view_product', args=[str(self.id)]) - @property def violates_sla(self): - findings = Finding.objects.filter(test__engagement__product=self, active=True) - for f in findings: - if f.violates_sla: - return True - return False + findings = Finding.objects.filter(test__engagement__product=self, + active=True, + sla_expiration_date__lt=timezone.now().date()) + return findings.count() > 0 class Product_Member(models.Model): @@ -2897,20 +2900,19 @@ def set_sla_expiration_date(self): self.sla_expiration_date = get_current_date() + relativedelta(days=days_remaining) def sla_days_remaining(self): - sla_calculation = None - sla_period = self.get_sla_period() - if sla_period: - sla_calculation = sla_period - self.sla_age - return sla_calculation - - def sla_deadline(self): - days_remaining = self.sla_days_remaining() - if days_remaining: + if self.sla_expiration_date: if self.mitigated: - return self.mitigated.date() + relativedelta(days=days_remaining) - return get_current_date() + relativedelta(days=days_remaining) + mitigated_date = self.mitigated + if isinstance(mitigated_date, datetime): + mitigated_date = self.mitigated.date() + return (self.sla_expiration_date - mitigated_date).days + else: + return (self.sla_expiration_date - get_current_date()).days return None + def sla_deadline(self): + return self.sla_expiration_date + def github(self): try: return self.github_issue @@ -3304,8 +3306,7 @@ def inherit_tags(self, potentially_existing_tags): @property def violates_sla(self): - days_remaining = self.sla_days_remaining() - return days_remaining < 0 if days_remaining else False + return (self.sla_expiration_date and self.sla_expiration_date < timezone.now()) class FindingAdmin(admin.ModelAdmin): diff --git a/dojo/pipeline.py b/dojo/pipeline.py index 0ce76220e98..130a795e092 100644 --- a/dojo/pipeline.py +++ b/dojo/pipeline.py @@ -98,7 +98,7 @@ def update_azure_groups(backend, uid, user=None, social=None, *args, **kwargs): except Exception as e: logger.error(f"Could not call microsoft graph API or save groups to member: {e}") if len(group_names) > 0: - assign_user_to_groups(user, group_names, 'AzureAD') + assign_user_to_groups(user, group_names, Dojo_Group.AZURE) if settings.AZUREAD_TENANT_OAUTH2_CLEANUP_GROUPS: cleanup_old_groups_for_user(user, group_names) diff --git a/dojo/remote_user.py b/dojo/remote_user.py index 875291c7ba2..7ed5f0a6a4a 100644 --- a/dojo/remote_user.py +++ b/dojo/remote_user.py @@ -6,6 +6,7 @@ from netaddr import IPAddress from django.conf import settings from dojo.pipeline import assign_user_to_groups, cleanup_old_groups_for_user +from dojo.models import Dojo_Group logger = logging.getLogger(__name__) @@ -77,7 +78,7 @@ def configure_user(self, request, user, created=True): if settings.AUTH_REMOTEUSER_GROUPS_HEADER and \ settings.AUTH_REMOTEUSER_GROUPS_HEADER in request.META: - assign_user_to_groups(user, request.META[settings.AUTH_REMOTEUSER_GROUPS_HEADER].split(','), 'Remote') + assign_user_to_groups(user, request.META[settings.AUTH_REMOTEUSER_GROUPS_HEADER].split(','), Dojo_Group.REMOTE) if settings.AUTH_REMOTEUSER_GROUPS_CLEANUP and \ settings.AUTH_REMOTEUSER_GROUPS_HEADER and \ diff --git a/dojo/templates/base.html b/dojo/templates/base.html index 8e42e4278a6..f4043d42e3c 100644 --- a/dojo/templates/base.html +++ b/dojo/templates/base.html @@ -407,6 +407,11 @@ {% trans "Product Type Counts" %}

  • +
  • + + {% trans "Product Tag Counts" %} + +
  • {% trans "Simple Metrics" %} diff --git a/dojo/templates/dojo/dashboard-metrics.html b/dojo/templates/dojo/dashboard-metrics.html index 2f62a8926aa..929bea53e92 100644 --- a/dojo/templates/dojo/dashboard-metrics.html +++ b/dojo/templates/dojo/dashboard-metrics.html @@ -176,7 +176,6 @@

    {% blocktrans with start_date=start_date.date end_date=end_date.date%}{{ nam - {% if punchcard %} diff --git a/dojo/templates/dojo/dashboard.html b/dojo/templates/dojo/dashboard.html index 8d3227f9759..8e049086094 100644 --- a/dojo/templates/dojo/dashboard.html +++ b/dojo/templates/dojo/dashboard.html @@ -207,7 +207,7 @@ {% else %} {% trans "View Responses" %} - {% trans "Create Engagement" %} + {% trans "Create Engagement" %} {% endif %} diff --git a/dojo/templates/dojo/endpoint_pdf_report.html b/dojo/templates/dojo/endpoint_pdf_report.html index d08e090173d..b53c8337422 100644 --- a/dojo/templates/dojo/endpoint_pdf_report.html +++ b/dojo/templates/dojo/endpoint_pdf_report.html @@ -279,7 +279,6 @@

    Notes
    - - - - {% if punchcard %} diff --git a/dojo/templates/dojo/product_metrics.html b/dojo/templates/dojo/product_metrics.html index 656bc4a8db1..d5c0b659757 100644 --- a/dojo/templates/dojo/product_metrics.html +++ b/dojo/templates/dojo/product_metrics.html @@ -489,7 +489,6 @@

    - {% include "dojo/filter_js_snippet.html" %} {% if punchcard %} diff --git a/dojo/templates/dojo/product_pdf_report.html b/dojo/templates/dojo/product_pdf_report.html index eb80b8148a2..aa413c72d1d 100644 --- a/dojo/templates/dojo/product_pdf_report.html +++ b/dojo/templates/dojo/product_pdf_report.html @@ -383,7 +383,6 @@

    Notes
    - {% if punchcard %} diff --git a/dojo/templates/dojo/product_type_pdf_report.html b/dojo/templates/dojo/product_type_pdf_report.html index 9bd22d587bc..f8c4175e5de 100644 --- a/dojo/templates/dojo/product_type_pdf_report.html +++ b/dojo/templates/dojo/product_type_pdf_report.html @@ -314,7 +314,6 @@
    Notes
    - - - {% block metrics %} {% endblock metrics %} diff --git a/dojo/tools/github_vulnerability/parser.py b/dojo/tools/github_vulnerability/parser.py index 15bf37606c9..3c134342d20 100644 --- a/dojo/tools/github_vulnerability/parser.py +++ b/dojo/tools/github_vulnerability/parser.py @@ -66,6 +66,9 @@ def get_findings(self, filename, test): if "vulnerableManifestPath" in alert: finding.file_path = alert["vulnerableManifestPath"] + if "vulnerableRequirements" in alert and alert["vulnerableRequirements"].startswith("= "): + finding.component_version = alert["vulnerableRequirements"][2:] + if "createdAt" in alert: finding.date = dateutil.parser.parse(alert["createdAt"]) diff --git a/dojo/tools/sarif/parser.py b/dojo/tools/sarif/parser.py index 14d81849570..e7963612b44 100644 --- a/dojo/tools/sarif/parser.py +++ b/dojo/tools/sarif/parser.py @@ -77,7 +77,10 @@ def __get_last_invocation_date(self, data): def get_rules(run): rules = {} - for item in run["tool"]["driver"].get("rules", []): + rules_array = run["tool"]["driver"].get("rules", []) + if len(rules_array) == 0 and run["tool"].get("extensions") is not None: + rules_array = run["tool"]["extensions"][0].get("rules", []) + for item in rules_array: rules[item["id"]] = item return rules diff --git a/dojo/utils.py b/dojo/utils.py index 135d341e54f..42334262d94 100644 --- a/dojo/utils.py +++ b/dojo/utils.py @@ -1082,7 +1082,7 @@ def get_period_counts(findings, } -def opened_in_period(start_date, end_date, pt): +def opened_in_period(start_date, end_date, **kwargs): start_date = datetime( start_date.year, start_date.month, @@ -1095,7 +1095,7 @@ def opened_in_period(start_date, end_date, pt): tzinfo=timezone.get_current_timezone()) opened_in_period = Finding.objects.filter( date__range=[start_date, end_date], - test__engagement__product__prod_type=pt, + **kwargs, verified=True, false_p=False, duplicate=False, @@ -1107,7 +1107,7 @@ def opened_in_period(start_date, end_date, pt): Count('numerical_severity')).order_by('numerical_severity') total_opened_in_period = Finding.objects.filter( date__range=[start_date, end_date], - test__engagement__product__prod_type=pt, + **kwargs, verified=True, false_p=False, duplicate=False, @@ -1139,7 +1139,7 @@ def opened_in_period(start_date, end_date, pt): 'closed': Finding.objects.filter( mitigated__date__range=[start_date, end_date], - test__engagement__product__prod_type=pt, + **kwargs, severity__in=('Critical', 'High', 'Medium', 'Low')).aggregate( total=Sum( Case( @@ -1155,7 +1155,7 @@ def opened_in_period(start_date, end_date, pt): duplicate=False, out_of_scope=False, mitigated__isnull=True, - test__engagement__product__prod_type=pt, + **kwargs, severity__in=('Critical', 'High', 'Medium', 'Low')).count() } diff --git a/helm/defectdojo/Chart.lock b/helm/defectdojo/Chart.lock index f332717cd06..7152b06eea7 100644 --- a/helm/defectdojo/Chart.lock +++ b/helm/defectdojo/Chart.lock @@ -1,7 +1,7 @@ dependencies: - name: mysql repository: https://charts.bitnami.com/bitnami - version: 9.1.8 + version: 9.19.1 - name: postgresql repository: https://charts.bitnami.com/bitnami version: 11.6.26 @@ -13,6 +13,6 @@ dependencies: version: 11.2.2 - name: redis repository: https://charts.bitnami.com/bitnami - version: 16.12.3 -digest: sha256:f53ebb0cea44dfbb72ac96ae98680848acd5e17a0947a728e5646460d0da4ef9 -generated: "2023-03-06T17:08:53.379497544Z" + version: 16.13.2 +digest: sha256:055c755109a79afc56850a8c742db9968c1ab1b64ea5b1c6c79dd26192ce14d3 +generated: "2024-02-15T03:24:53.319013122Z" diff --git a/helm/defectdojo/Chart.yaml b/helm/defectdojo/Chart.yaml index 53bce7bc759..9e1c002fe58 100644 --- a/helm/defectdojo/Chart.yaml +++ b/helm/defectdojo/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 appVersion: "2.32.0-dev" description: A Helm chart for Kubernetes to install DefectDojo name: defectdojo -version: 1.6.109-dev +version: 1.6.110-dev icon: https://www.defectdojo.org/img/favicon.ico maintainers: - name: madchap @@ -10,23 +10,23 @@ maintainers: url: https://github.com/DefectDojo/django-DefectDojo dependencies: - name: mysql - version: ~9.1.7 - repository: "@bitnami" + version: ~9.19.0 + repository: "https://charts.bitnami.com/bitnami" condition: mysql.enabled - name: postgresql version: ~11.6.5 - repository: "@bitnami" + repository: "https://charts.bitnami.com/bitnami" condition: postgresql.enabled - name: postgresql-ha version: ~9.1.5 - repository: "@bitnami" + repository: "https://charts.bitnami.com/bitnami" alias: postgresqlha condition: postgresqlha.enabled - name: rabbitmq version: ~11.2.0 - repository: "@bitnami" + repository: "https://charts.bitnami.com/bitnami" condition: rabbitmq.enabled - name: redis - version: ~16.12.0 - repository: "@bitnami" + version: ~16.13.0 + repository: "https://charts.bitnami.com/bitnami" condition: redis.enabled diff --git a/unittests/scans/github_vulnerability/github-vuln-version.json b/unittests/scans/github_vulnerability/github-vuln-version.json new file mode 100644 index 00000000000..e80afe7e583 --- /dev/null +++ b/unittests/scans/github_vulnerability/github-vuln-version.json @@ -0,0 +1,106 @@ +{ + "data": { + "repository": { + "vulnerabilityAlerts": { + "nodes": [ + { + "id": "RVA_kwDOLJyUo88AAAABQUWapw", + "createdAt": "2024-01-26T02:42:32Z", + "vulnerableManifestPath": "sompath/pom.xml", + "securityVulnerability": { + "severity": "CRITICAL", + "updatedAt": "2022-12-09T22:02:22Z", + "package": { + "name": "org.springframework:spring-web", + "ecosystem": "MAVEN" + }, + "firstPatchedVersion": { + "identifier": "6.0.0" + }, + "vulnerableVersionRange": "< 6.0.0", + "advisory": { + "description": "Pivotal Spring Framework before 6.0.0 suffers from a potential remote code execution (RCE) issue if used for Java deserialization of untrusted data. Depending on how the library is implemented within a product, this issue may or not occur, and authentication may be required.\n\nMaintainers recommend investigating alternative components or a potential mitigating control. Version 4.2.6 and 3.2.17 contain [enhanced documentation](https://github.com/spring-projects/spring-framework/commit/5cbe90b2cd91b866a5a9586e460f311860e11cfa) advising users to take precautions against unsafe Java deserialization, version 5.3.0 [deprecate the impacted classes](https://github.com/spring-projects/spring-framework/issues/25379) and version 6.0.0 [removed it entirely](https://github.com/spring-projects/spring-framework/issues/27422).", + "summary": "Pivotal Spring Framework contains unsafe Java deserialization methods", + "identifiers": [ + { + "value": "GHSA-4wrc-f8pq-fpqp", + "type": "GHSA" + }, + { + "value": "CVE-2016-1000027", + "type": "CVE" + } + ], + "references": [ + { + "url": "https://nvd.nist.gov/vuln/detail/CVE-2016-1000027" + }, + { + "url": "https://bugzilla.redhat.com/show_bug.cgi?id=CVE-2016-1000027" + }, + { + "url": "https://security-tracker.debian.org/tracker/CVE-2016-1000027" + }, + { + "url": "https://www.tenable.com/security/research/tra-2016-20" + }, + { + "url": "https://github.com/spring-projects/spring-framework/issues/24434" + }, + { + "url": "https://github.com/spring-projects/spring-framework/issues/24434#issuecomment-1231625331" + }, + { + "url": "https://github.com/spring-projects/spring-framework/commit/5cbe90b2cd91b866a5a9586e460f311860e11cfa" + }, + { + "url": "https://support.contrastsecurity.com/hc/en-us/articles/4402400830612-Spring-web-Java-Deserialization-CVE-2016-1000027" + }, + { + "url": "https://github.com/spring-projects/spring-framework/issues/21680" + }, + { + "url": "https://github.com/spring-projects/spring-framework/commit/2b051b8b321768a4cfef83077db65c6328ffd60f" + }, + { + "url": "https://jira.spring.io/browse/SPR-17143?redirect=false" + }, + { + "url": "https://github.com/spring-projects/spring-framework/issues/24434#issuecomment-579669626" + }, + { + "url": "https://github.com/spring-projects/spring-framework/issues/24434#issuecomment-582313417" + }, + { + "url": "https://github.com/spring-projects/spring-framework/issues/24434#issuecomment-744519525" + }, + { + "url": "https://security.netapp.com/advisory/ntap-20230420-0009/" + }, + { + "url": "https://spring.io/blog/2022/05/11/spring-framework-5-3-20-and-5-2-22-available-now" + }, + { + "url": "https://github.com/advisories/GHSA-4wrc-f8pq-fpqp" + } + ], + "cvss": { + "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" + } + } + }, + "state": "OPEN", + "vulnerableManifestFilename": "pom.xml", + "vulnerableRequirements": "= 5.3.29", + "number": 1, + "dependencyScope": "RUNTIME", + "dismissComment": null, + "dismissReason": null, + "dismissedAt": null, + "fixedAt": null + } + ] + } + } + } +} diff --git a/unittests/test_remote_user.py b/unittests/test_remote_user.py index 384e4dda75b..d764358e11e 100644 --- a/unittests/test_remote_user.py +++ b/unittests/test_remote_user.py @@ -16,8 +16,8 @@ def setUp(self): last_name='original_last', email='original@mail.com', ) - self.group1, _ = Dojo_Group.objects.get_or_create(name="group1", social_provider="Remote") - self.group2, _ = Dojo_Group.objects.get_or_create(name="group2", social_provider="Remote") + self.group1, _ = Dojo_Group.objects.get_or_create(name="group1", social_provider=Dojo_Group.REMOTE) + self.group2, _ = Dojo_Group.objects.get_or_create(name="group2", social_provider=Dojo_Group.REMOTE) @override_settings(AUTH_REMOTEUSER_ENABLED=False) def test_disabled(self): diff --git a/unittests/test_swagger_schema.py b/unittests/test_swagger_schema.py index 9f1316b4d2e..b1263359374 100644 --- a/unittests/test_swagger_schema.py +++ b/unittests/test_swagger_schema.py @@ -785,6 +785,9 @@ def __init__(self, *args, **kwargs): self.viewset = ToolTypesViewSet self.model = Tool_Type self.serializer = ToolTypeSerializer + self.field_transformers = { + "name": lambda v: v + "_new" + } class UserTest(BaseClass.SchemaTest): diff --git a/unittests/tools/test_github_vulnerability_parser.py b/unittests/tools/test_github_vulnerability_parser.py index acc955e3492..1453c02a39b 100644 --- a/unittests/tools/test_github_vulnerability_parser.py +++ b/unittests/tools/test_github_vulnerability_parser.py @@ -251,3 +251,18 @@ def test_parse_state(self): self.assertEqual(finding.file_path, "apache/cxf/cxf-shiro/pom.xml") self.assertEqual(finding.active, False) self.assertEqual(finding.is_mitigated, True) + + def test_parser_version(self): + testfile = open("unittests/scans/github_vulnerability/github-vuln-version.json") + parser = GithubVulnerabilityParser() + findings = parser.get_findings(testfile, Test()) + self.assertEqual(1, len(findings)) + for finding in findings: + finding.clean() + + with self.subTest(i=0): + finding = findings[0] + self.assertEqual(finding.title, "Pivotal Spring Framework contains unsafe Java deserialization methods") + self.assertEqual(finding.severity, "Critical") + self.assertEqual(finding.component_name, "org.springframework:spring-web") + self.assertEqual(finding.component_version, "5.3.29") From 9fa8936a0542325b594e4dca10857b3ab41aaec9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quirin=20Hardy=20Zie=C3=9Fler?= Date: Thu, 15 Feb 2024 23:11:11 +0100 Subject: [PATCH 22/22] update epss-score (#5) solve conflicts --- helm/defectdojo/Chart.lock | 10 +++++----- helm/defectdojo/Chart.yaml | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/helm/defectdojo/Chart.lock b/helm/defectdojo/Chart.lock index 7152b06eea7..c90ddac953f 100644 --- a/helm/defectdojo/Chart.lock +++ b/helm/defectdojo/Chart.lock @@ -4,15 +4,15 @@ dependencies: version: 9.19.1 - name: postgresql repository: https://charts.bitnami.com/bitnami - version: 11.6.26 + version: 11.9.13 - name: postgresql-ha repository: https://charts.bitnami.com/bitnami - version: 9.1.9 + version: 9.4.11 - name: rabbitmq repository: https://charts.bitnami.com/bitnami - version: 11.2.2 + version: 11.16.2 - name: redis repository: https://charts.bitnami.com/bitnami version: 16.13.2 -digest: sha256:055c755109a79afc56850a8c742db9968c1ab1b64ea5b1c6c79dd26192ce14d3 -generated: "2024-02-15T03:24:53.319013122Z" +digest: sha256:50d07c49c1fb199a70fafd032712a1d5509a0352f090bfddd2e8a22b35be0961 +generated: "2024-02-15T20:24:24.560785941Z" \ No newline at end of file diff --git a/helm/defectdojo/Chart.yaml b/helm/defectdojo/Chart.yaml index 9e1c002fe58..1c44736dafb 100644 --- a/helm/defectdojo/Chart.yaml +++ b/helm/defectdojo/Chart.yaml @@ -14,16 +14,16 @@ dependencies: repository: "https://charts.bitnami.com/bitnami" condition: mysql.enabled - name: postgresql - version: ~11.6.5 + version: ~11.9.0 repository: "https://charts.bitnami.com/bitnami" condition: postgresql.enabled - name: postgresql-ha - version: ~9.1.5 + version: ~9.4.0 repository: "https://charts.bitnami.com/bitnami" alias: postgresqlha condition: postgresqlha.enabled - name: rabbitmq - version: ~11.2.0 + version: ~11.16.0 repository: "https://charts.bitnami.com/bitnami" condition: rabbitmq.enabled - name: redis