Skip to content

Commit

Permalink
feat: enhance target and organization management with validation and …
Browse files Browse the repository at this point in the history
…tests

- Imports and Dependencies: Reorganized and added necessary imports for better modularity and functionality.
- File Upload Validation: Added checks for empty file uploads and invalid IP addresses during target addition.
- Error Handling: Improved error handling in delete_target and delete_organization views to handle non-existent entities.
- Form Enhancements: Updated AddOrganizationForm to use ModelForm and improved domain selection logic.
- Utility Functions: Moved get_ip_info and get_ips_from_cidr_range functions to common_func.py for better reusability.
- Validators: Added a new validate_ip function in validators.py to validate IP addresses.
- Unit Tests: Added comprehensive unit tests for target and organization views to ensure proper functionality and error handling.
  • Loading branch information
psyray committed Sep 9, 2024
1 parent 401b77b commit a7f0372
Show file tree
Hide file tree
Showing 6 changed files with 565 additions and 67 deletions.
39 changes: 39 additions & 0 deletions web/reNgine/common_func.py
Original file line number Diff line number Diff line change
Expand Up @@ -1200,3 +1200,42 @@ def safe_int_cast(value, default=None):
return int(value)
except (ValueError, TypeError):
return default

def get_ip_info(ip_address):
"""
get_ip_info retrieves information about a given IP address, determining whether it is an IPv4 or IPv6 address. It returns an appropriate IP address object if the input is valid, or None if the input is not a valid IP address.
Args:
ip_address (str): The IP address to validate and retrieve information for.
Returns:
IPv4Address or IPv6Address or None: An IP address object if the input is valid, otherwise None.
"""
is_ipv4 = bool(validators.ipv4(ip_address))
is_ipv6 = bool(validators.ipv6(ip_address))
ip_data = None
if is_ipv4:
ip_data = ipaddress.IPv4Address(ip_address)
elif is_ipv6:
ip_data = ipaddress.IPv6Address(ip_address)
else:
return None
return ip_data

def get_ips_from_cidr_range(target):

Check notice

Code scanning / CodeQL

Explicit returns mixed with implicit (fall through) returns Note

Mixing implicit and explicit returns may indicate an error as implicit returns always return None.
"""
get_ips_from_cidr_range generates a list of IP addresses from a given CIDR range. It returns the list of valid IPv4 addresses or logs an error if the provided CIDR range is invalid.
Args:
target (str): The CIDR range from which to generate IP addresses.
Returns:
list of str: A list of IP addresses as strings if the CIDR range is valid; otherwise, an empty list is returned.
Raises:
ValueError: If the target is not a valid CIDR range, an error is logged.
"""
try:
return [str(ip) for ip in ipaddress.IPv4Network(target)]
except Exception as e:
logger.error(f'{target} is not a valid CIDR range. Skipping.')
5 changes: 5 additions & 0 deletions web/reNgine/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,8 @@ def validate_short_name(value):
raise ValidationError(_('%(value)s is not a valid short name,'
+ ' can only contain - and _'),
params={'value': value})

def validate_ip(ip):
"""Validate if the given IP address is valid."""
if not (validators.ipv4(ip) or validators.ipv6(ip)):
raise ValidationError(_('Invalid IP address: %(ip)s'), params={'ip': ip})
46 changes: 23 additions & 23 deletions web/targetApp/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,28 @@ class AddTargetForm(forms.Form):
}
))

class AddOrganizationForm(forms.Form):
class AddOrganizationForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
project = kwargs.pop('project')
super(AddOrganizationForm, self).__init__(*args, **kwargs)
self.fields['domains'].choices = [(domain.id, domain.name) for domain in Domain.objects.filter(project__slug=project) if not domain.get_organization()]

self.fields['domains'] = forms.ModelMultipleChoiceField(
queryset=Domain.objects.filter(project__slug=project, domains__isnull=True),
widget=forms.SelectMultiple(
attrs={
"class": "form-control select2-multiple",
"data-toggle": "select2",
"data-width": "100%",
"data-placeholder": "Choose Targets",
"id": "domains",
}
),
required=True
)

class Meta:
model = Organization
fields = ['name', 'description', 'domains']

name = forms.CharField(
required=True,
widget=forms.TextInput(
Expand All @@ -68,26 +84,10 @@ def __init__(self, *args, **kwargs):
}
))

domains = forms.ChoiceField(
required=True,
widget=forms.Select(
attrs={
"class": "form-control select2-multiple",
"multiple": "multiple",
"data-toggle": "select2",
"data-width": "100%",
"multiple": "multiple",
"data-placeholder": "Choose Targets",
"id": "domains",
}
)
)

def clean_name(self):
data = self.cleaned_data['name']
if Organization.objects.filter(name=data).count() > 0:
raise forms.ValidationError(f"{data} Organization already exists")
return data
def clean_domains(self):
if domains := self.cleaned_data.get('domains'):
return [int(domain.id) for domain in domains]
return []


class UpdateTargetForm(forms.ModelForm):
Expand Down
2 changes: 2 additions & 0 deletions web/targetApp/tests/__init__.py