2626from django .core .files .base import ContentFile
2727from django .core .validators import MaxValueValidator , MinValueValidator , RegexValidator , validate_ipv46_address
2828from django .db import connection , models
29- from django .db .models import Count , F , JSONField , Q
29+ from django .db .models import Count , F , Q
3030from django .db .models .expressions import Case , When
3131from django .db .models .functions import Lower
3232from django .urls import reverse
6666# default template with all values set to 0
6767DEFAULT_STATS = {sev .lower (): dict .fromkeys (STATS_FIELDS , 0 ) for sev in SEVERITIES }
6868
69- IMPORT_CREATED_FINDING = "N"
70- IMPORT_CLOSED_FINDING = "C"
71- IMPORT_REACTIVATED_FINDING = "R"
72- IMPORT_UNTOUCHED_FINDING = "U"
73-
74- IMPORT_ACTIONS = [
75- (IMPORT_CREATED_FINDING , "created" ),
76- (IMPORT_CLOSED_FINDING , "closed" ),
77- (IMPORT_REACTIVATED_FINDING , "reactivated" ),
78- (IMPORT_UNTOUCHED_FINDING , "untouched" ),
79- ]
80-
8169
8270def _get_annotations_for_statistics ():
8371 annotations = {stats_field .lower (): Count (Case (When (** {stats_field : True }, then = 1 ))) for stats_field in STATS_FIELDS if stats_field != "total" }
@@ -759,6 +747,17 @@ def clean(self):
759747
760748
761749from dojo .product_type .models import Product_Type # noqa: E402 -- re-export; mid-file as Product FK uses it below
750+ from dojo .test .models import ( # noqa: E402 -- re-export; class-body FKs below reference these
751+ IMPORT_ACTIONS , # noqa: F401 -- re-export
752+ IMPORT_CLOSED_FINDING , # noqa: F401 -- re-export
753+ IMPORT_CREATED_FINDING , # noqa: F401 -- re-export
754+ IMPORT_REACTIVATED_FINDING , # noqa: F401 -- re-export
755+ IMPORT_UNTOUCHED_FINDING , # noqa: F401 -- re-export
756+ Test ,
757+ Test_Import , # noqa: F401 -- re-export
758+ Test_Import_Finding_Action , # noqa: F401 -- re-export
759+ Test_Type ,
760+ )
762761
763762
764763class Product_Line (models .Model ):
@@ -773,26 +772,6 @@ class Report_Type(models.Model):
773772 name = models .CharField (max_length = 255 )
774773
775774
776- class Test_Type (models .Model ):
777- name = models .CharField (max_length = 200 , unique = True )
778- static_tool = models .BooleanField (default = False )
779- dynamic_tool = models .BooleanField (default = False )
780- active = models .BooleanField (default = True )
781- dynamically_generated = models .BooleanField (
782- default = False ,
783- help_text = _ ("Set to True for test types that are created at import time" ))
784-
785- class Meta :
786- ordering = ("name" ,)
787-
788- def __str__ (self ):
789- return self .name
790-
791- def get_breadcrumbs (self ):
792- return [{"title" : str (self ),
793- "url" : None }]
794-
795-
796775class DojoMeta (models .Model ):
797776 name = models .CharField (max_length = 120 )
798777 value = models .CharField (max_length = 300 )
@@ -1978,235 +1957,6 @@ class Meta:
19781957 ordering = ("-created" , )
19791958
19801959
1981- class Test (models .Model ):
1982- engagement = models .ForeignKey (Engagement , editable = False , on_delete = models .CASCADE )
1983- lead = models .ForeignKey (Dojo_User , editable = True , null = True , blank = True , on_delete = models .RESTRICT )
1984- test_type = models .ForeignKey (Test_Type , on_delete = models .CASCADE )
1985- scan_type = models .TextField (null = True )
1986- title = models .CharField (max_length = 255 , null = True , blank = True )
1987- description = models .TextField (null = True , blank = True )
1988- target_start = models .DateTimeField ()
1989- target_end = models .DateTimeField ()
1990- percent_complete = models .IntegerField (null = True , blank = True ,
1991- editable = True )
1992- notes = models .ManyToManyField (Notes , blank = True ,
1993- editable = False )
1994- files = models .ManyToManyField (FileUpload , blank = True , editable = False )
1995- environment = models .ForeignKey (Development_Environment , null = True ,
1996- blank = False , on_delete = models .RESTRICT )
1997-
1998- updated = models .DateTimeField (auto_now = True , null = True )
1999- created = models .DateTimeField (auto_now_add = True , null = True )
2000-
2001- tags = TagField (blank = True , force_lowercase = True , help_text = _ ("Add tags that help describe this test. Choose from the list or add new tags. Press Enter key to add." ))
2002- inherited_tags = TagField (blank = True , force_lowercase = True , help_text = _ ("Internal use tags sepcifically for maintaining parity with product. This field will be present as a subset in the tags field" ))
2003-
2004- version = models .CharField (max_length = 100 , null = True , blank = True )
2005-
2006- build_id = models .CharField (editable = True , max_length = 150 ,
2007- null = True , blank = True , help_text = _ ("Build ID that was tested, a reimport may update this field." ), verbose_name = _ ("Build ID" ))
2008- commit_hash = models .CharField (editable = True , max_length = 150 ,
2009- null = True , blank = True , help_text = _ ("Commit hash tested, a reimport may update this field." ), verbose_name = _ ("Commit Hash" ))
2010- branch_tag = models .CharField (editable = True , max_length = 150 ,
2011- null = True , blank = True , help_text = _ ("Tag or branch that was tested, a reimport may update this field." ), verbose_name = _ ("Branch/Tag" ))
2012- api_scan_configuration = models .ForeignKey (Product_API_Scan_Configuration , null = True , editable = True , blank = True , on_delete = models .CASCADE , verbose_name = _ ("API Scan Configuration" ))
2013-
2014- class Meta :
2015- indexes = [
2016- models .Index (fields = ["engagement" , "test_type" ]),
2017- ]
2018-
2019- def __init__ (self , * args , ** kwargs ):
2020- super ().__init__ (* args , ** kwargs )
2021- self .unsaved_metadata : list = []
2022-
2023- def __str__ (self ):
2024- if self .title :
2025- return f"{ self .title } ({ self .test_type } )"
2026- return str (self .test_type )
2027-
2028- def get_absolute_url (self ):
2029- return reverse ("view_test" , args = [str (self .id )])
2030-
2031- def test_type_name (self ) -> str :
2032- return self .test_type .name
2033-
2034- def get_breadcrumbs (self ):
2035- bc = self .engagement .get_breadcrumbs ()
2036- bc += [{"title" : str (self ),
2037- "url" : reverse ("view_test" , args = (self .id ,))}]
2038- return bc
2039-
2040- def copy (self , engagement = None ):
2041- copy = copy_model_util (self )
2042- # Save the necessary ManyToMany relationships
2043- old_notes = list (self .notes .all ())
2044- old_files = list (self .files .all ())
2045- old_tags = list (self .tags .all ())
2046- old_findings = list (Finding .objects .filter (test = self ))
2047- if engagement :
2048- copy .engagement = engagement
2049- # Save the object before setting any ManyToMany relationships
2050- copy .save ()
2051- # Copy the notes
2052- for notes in old_notes :
2053- copy .notes .add (notes .copy ())
2054- # Copy the files
2055- for files in old_files :
2056- copy .files .add (files .copy ())
2057- # Copy the Findings
2058- for finding in old_findings :
2059- finding .copy (test = copy )
2060- # Assign any tags
2061- copy .tags .set (old_tags )
2062-
2063- return copy
2064-
2065- # only used by bulk risk acceptance api
2066- @property
2067- def unaccepted_open_findings (self ):
2068- from dojo .utils import get_system_setting # noqa: PLC0415 circular import
2069- findings = Finding .objects .filter (risk_accepted = False , active = True , duplicate = False , test = self )
2070- if get_system_setting ("enforce_verified_status" , True ) or get_system_setting ("enforce_verified_status_metrics" , True ):
2071- findings = findings .filter (verified = True )
2072-
2073- return findings
2074-
2075- def accept_risks (self , accepted_risks ):
2076- self .engagement .risk_acceptance .add (* accepted_risks )
2077-
2078- @property
2079- def deduplication_algorithm (self ):
2080- deduplicationAlgorithm = settings .DEDUPE_ALGO_LEGACY
2081-
2082- if hasattr (settings , "DEDUPLICATION_ALGORITHM_PER_PARSER" ):
2083- if (self .test_type .name in settings .DEDUPLICATION_ALGORITHM_PER_PARSER ):
2084- deduplicationLogger .debug (f"using DEDUPLICATION_ALGORITHM_PER_PARSER for test_type.name: { self .test_type .name } " )
2085- deduplicationAlgorithm = settings .DEDUPLICATION_ALGORITHM_PER_PARSER [self .test_type .name ]
2086- elif (self .scan_type in settings .DEDUPLICATION_ALGORITHM_PER_PARSER ):
2087- deduplicationLogger .debug (f"using DEDUPLICATION_ALGORITHM_PER_PARSER for scan_type: { self .scan_type } " )
2088- deduplicationAlgorithm = settings .DEDUPLICATION_ALGORITHM_PER_PARSER [self .scan_type ]
2089- else :
2090- deduplicationLogger .debug ("Section DEDUPLICATION_ALGORITHM_PER_PARSER not found in settings.dist.py" )
2091-
2092- deduplicationLogger .debug (f"DEDUPLICATION_ALGORITHM_PER_PARSER is: { deduplicationAlgorithm } " )
2093- return deduplicationAlgorithm
2094-
2095- @property
2096- def hash_code_fields (self ):
2097- """Retrieve OS HASH_CODE_FIELDS_PER_SCANNER settings. Be aware when calling this to make sure Pro doesn't use these OS seetings"""
2098- hashCodeFields = None
2099-
2100- if hasattr (settings , "HASHCODE_FIELDS_PER_SCANNER" ):
2101- if (self .test_type .name in settings .HASHCODE_FIELDS_PER_SCANNER ):
2102- deduplicationLogger .debug (f"using HASHCODE_FIELDS_PER_SCANNER for test_type.name: { self .test_type .name } " )
2103- hashCodeFields = settings .HASHCODE_FIELDS_PER_SCANNER [self .test_type .name ]
2104- elif (self .scan_type in settings .HASHCODE_FIELDS_PER_SCANNER ):
2105- deduplicationLogger .debug (f"using HASHCODE_FIELDS_PER_SCANNER for scan_type: { self .scan_type } " )
2106- hashCodeFields = settings .HASHCODE_FIELDS_PER_SCANNER [self .scan_type ]
2107- else :
2108- deduplicationLogger .warning (f"test_type name { self .test_type .name } and scan_type { self .scan_type } not found in HASHCODE_FIELDS_PER_SCANNER" )
2109- else :
2110- deduplicationLogger .debug ("Section HASHCODE_FIELDS_PER_SCANNER not found in settings.dist.py" )
2111-
2112- hash_code_fields_always = getattr (settings , "HASH_CODE_FIELDS_ALWAYS" , [])
2113- deduplicationLogger .debug (f"HASHCODE_FIELDS_PER_SCANNER is: { hashCodeFields } + HASH_CODE_FIELDS_ALWAYS: { hash_code_fields_always } " )
2114-
2115- return hashCodeFields
2116-
2117- @property
2118- def hash_code_allows_null_cwe (self ):
2119- hashCodeAllowsNullCwe = True
2120-
2121- if hasattr (settings , "HASHCODE_ALLOWS_NULL_CWE" ):
2122- if (self .test_type .name in settings .HASHCODE_ALLOWS_NULL_CWE ):
2123- deduplicationLogger .debug (f"using HASHCODE_ALLOWS_NULL_CWE for test_type.name: { self .test_type .name } " )
2124- hashCodeAllowsNullCwe = settings .HASHCODE_ALLOWS_NULL_CWE [self .test_type .name ]
2125- elif (self .scan_type in settings .HASHCODE_ALLOWS_NULL_CWE ):
2126- deduplicationLogger .debug (f"using HASHCODE_ALLOWS_NULL_CWE for scan_type: { self .scan_type } " )
2127- hashCodeAllowsNullCwe = settings .HASHCODE_ALLOWS_NULL_CWE [self .scan_type ]
2128- else :
2129- deduplicationLogger .debug ("Section HASHCODE_ALLOWS_NULL_CWE not found in settings.dist.py" )
2130-
2131- deduplicationLogger .debug (f"HASHCODE_ALLOWS_NULL_CWE is: { hashCodeAllowsNullCwe } " )
2132- return hashCodeAllowsNullCwe
2133-
2134- def delete (self , * args , product_grading_option = True , ** kwargs ):
2135- logger .debug ("%d test delete" , self .id )
2136- super ().delete (* args , ** kwargs )
2137- if product_grading_option :
2138- with suppress (Test .DoesNotExist , Engagement .DoesNotExist , Product .DoesNotExist ):
2139- # Suppressing a potential issue created from async delete removing
2140- # related objects in a separate task
2141- from dojo .utils import perform_product_grading # noqa: PLC0415 circular import
2142- perform_product_grading (self .engagement .product )
2143-
2144- @property
2145- def statistics (self ):
2146- """Queries the database, no prefetching, so could be slow for lists of model instances"""
2147- return _get_statistics_for_queryset (Finding .objects .filter (test = self ), _get_annotations_for_statistics )
2148-
2149-
2150- class Test_Import (TimeStampedModel ):
2151-
2152- IMPORT_TYPE = "import"
2153- REIMPORT_TYPE = "reimport"
2154-
2155- test = models .ForeignKey (Test , editable = False , null = False , blank = False , on_delete = models .CASCADE )
2156- findings_affected = models .ManyToManyField ("Finding" , through = "Test_Import_Finding_Action" )
2157- import_settings = JSONField (null = True )
2158- type = models .CharField (max_length = 64 , null = False , blank = False , default = "unknown" )
2159-
2160- version = models .CharField (max_length = 100 , null = True , blank = True )
2161- build_id = models .CharField (editable = True , max_length = 150 ,
2162- null = True , blank = True , help_text = _ ("Build ID that was tested, a reimport may update this field." ), verbose_name = _ ("Build ID" ))
2163- commit_hash = models .CharField (editable = True , max_length = 150 ,
2164- null = True , blank = True , help_text = _ ("Commit hash tested, a reimport may update this field." ), verbose_name = _ ("Commit Hash" ))
2165- branch_tag = models .CharField (editable = True , max_length = 150 ,
2166- null = True , blank = True , help_text = _ ("Tag or branch that was tested, a reimport may update this field." ), verbose_name = _ ("Branch/Tag" ))
2167-
2168- def get_queryset (self ):
2169- logger .debug ("prefetch test_import counts" )
2170- super_query = super ().get_queryset ()
2171- super_query = super_query .annotate (created_findings_count = Count ("findings" , filter = Q (test_import_finding_action__action = IMPORT_CREATED_FINDING )))
2172- super_query = super_query .annotate (closed_findings_count = Count ("findings" , filter = Q (test_import_finding_action__action = IMPORT_CLOSED_FINDING )))
2173- super_query = super_query .annotate (reactivated_findings_count = Count ("findings" , filter = Q (test_import_finding_action__action = IMPORT_REACTIVATED_FINDING )))
2174- return super_query .annotate (untouched_findings_count = Count ("findings" , filter = Q (test_import_finding_action__action = IMPORT_UNTOUCHED_FINDING )))
2175-
2176- class Meta :
2177- ordering = ("-id" ,)
2178- indexes = [
2179- models .Index (fields = ["created" , "test" , "type" ]),
2180- ]
2181-
2182- def __str__ (self ):
2183- return self .created .strftime ("%Y-%m-%d %H:%M:%S" )
2184-
2185- @property
2186- def statistics (self ):
2187- """Queries the database, no prefetching, so could be slow for lists of model instances"""
2188- stats = {}
2189- for action in IMPORT_ACTIONS :
2190- stats [action [1 ].lower ()] = _get_statistics_for_queryset (Finding .objects .filter (test_import_finding_action__test_import = self , test_import_finding_action__action = action [0 ]), _get_annotations_for_statistics )
2191- return stats
2192-
2193-
2194- class Test_Import_Finding_Action (TimeStampedModel ):
2195- test_import = models .ForeignKey (Test_Import , editable = False , null = False , blank = False , on_delete = models .CASCADE )
2196- finding = models .ForeignKey ("Finding" , editable = False , null = False , blank = False , on_delete = models .CASCADE )
2197- action = models .CharField (max_length = 100 , null = True , blank = True , choices = IMPORT_ACTIONS )
2198-
2199- class Meta :
2200- indexes = [
2201- models .Index (fields = ["finding" , "action" , "test_import" ]),
2202- ]
2203- unique_together = (("test_import" , "finding" ))
2204- ordering = ("test_import" , "action" , "finding" )
2205-
2206- def __str__ (self ):
2207- return f"{ self .finding .id } : { self .action } "
2208-
2209-
22101960class Finding (BaseModel ):
22111961 # Fields loaded when performing deduplication (used by get_finding_models_for_deduplication
22121962 # and build_candidate_scope_queryset to restrict the SELECT to only what is needed).
@@ -4352,14 +4102,12 @@ def __str__(self):
43524102admin .site .register (Languages )
43534103admin .site .register (Language_Type )
43544104admin .site .register (App_Analysis )
4355- admin .site .register (Test )
43564105admin .site .register (Finding , FindingAdmin )
43574106admin .site .register (FileUpload )
43584107admin .site .register (FileAccessToken )
43594108admin .site .register (Engagement )
43604109admin .site .register (Risk_Acceptance )
43614110admin .site .register (Check_List )
4362- admin .site .register (Test_Type )
43634111admin .site .register (Endpoint_Params )
43644112admin .site .register (Endpoint_Status )
43654113admin .site .register (Endpoint )
@@ -4414,6 +4162,4 @@ def __str__(self):
44144162admin .site .register (BannerConf )
44154163admin .site .register (Tool_Product_History )
44164164admin .site .register (General_Survey )
4417- admin .site .register (Test_Import )
4418- admin .site .register (Test_Import_Finding_Action )
44194165admin .site .register (Finding_Group )
0 commit comments