55from django .core .exceptions import FieldDoesNotExist
66from rest_framework import serializers
77
8- from greedybear .consts import REGEX_DOMAIN , REGEX_IP
9- from greedybear .models import IOC , GeneralHoneypot
8+ from greedybear .consts import REGEX_DOMAIN
9+ from greedybear .models import IOC , GeneralHoneypot , Sensor , Tag
10+ from greedybear .utils import is_ip_address
1011
1112logger = logging .getLogger (__name__ )
1213
@@ -19,8 +20,22 @@ def to_representation(self, value):
1920 return value .name
2021
2122
23+ class TagSerializer (serializers .ModelSerializer ):
24+ class Meta :
25+ model = Tag
26+ fields = ["key" , "value" , "source" ]
27+
28+
29+ class SensorSerializer (serializers .ModelSerializer ):
30+ class Meta :
31+ model = Sensor
32+ fields = ["address" , "label" ]
33+
34+
2235class IOCSerializer (serializers .ModelSerializer ):
2336 general_honeypot = GeneralHoneypotSerializer (many = True , read_only = True )
37+ tags = TagSerializer (many = True , read_only = True )
38+ sensors = SensorSerializer (many = True , read_only = True )
2439
2540 class Meta :
2641 model = IOC
@@ -36,22 +51,38 @@ class EnrichmentSerializer(serializers.Serializer):
3651
3752 def validate (self , data ):
3853 """
39- Check a given observable against regex expression
54+ Validate that the query is a valid IP address (IPv4/IPv6) or domain.
4055 """
41- observable = data ["query" ]
42- if re .match (r"^[\d\.]+$" , observable ) and not re .match (REGEX_IP , observable ):
43- raise serializers .ValidationError ("Observable is not a valid IP" )
44- if not re .match (REGEX_IP , observable ) and not re .match (REGEX_DOMAIN , observable ):
45- raise serializers .ValidationError ("Observable is not a valid IP or domain" )
56+ observable = data ["query" ].strip ()
57+ data ["query" ] = observable
58+
59+ # A valid domain must match the domain regex AND contain at least one alphabetic character
60+ is_domain = bool (re .match (REGEX_DOMAIN , observable )) and any (c .isalpha () for c in observable )
61+
62+ if not is_ip_address (observable ) and not is_domain :
63+ raise serializers .ValidationError ("Observable is not a valid IP address or domain" )
64+
4665 try :
47- required_object = IOC .objects .get (name = observable )
66+ required_object = IOC .objects .prefetch_related ( "tags" , "sensors" ). get (name = observable )
4867 data ["found" ] = True
4968 data ["ioc" ] = required_object
5069 except IOC .DoesNotExist :
5170 data ["found" ] = False
5271 return data
5372
5473
74+ def parse_feed_types (feed_type_str : str ) -> list :
75+ """Split a comma-separated feed type string into a stripped list of individual feed types.
76+
77+ Args:
78+ feed_type_str (str): Comma-separated feed type string (e.g. "cowrie,adbhoney").
79+
80+ Returns:
81+ list[str]: List of non-empty, stripped feed type tokens.
82+ """
83+ return [ft .strip () for ft in feed_type_str .split ("," ) if ft .strip ()]
84+
85+
5586def feed_type_validation (feed_type : str , valid_feed_types : frozenset ) -> str :
5687 """Validates that a given feed type exists in the set of valid feed types.
5788
@@ -96,7 +127,7 @@ def ordering_validation(ordering: str) -> str:
96127
97128
98129class FeedsRequestSerializer (serializers .Serializer ):
99- feed_type = serializers .CharField (max_length = 120 )
130+ feed_type = serializers .CharField ()
100131 attack_type = serializers .ChoiceField (choices = ["scanner" , "payload_request" , "all" ])
101132 ioc_type = serializers .ChoiceField (choices = ["ip" , "domain" , "all" ])
102133 max_age = serializers .IntegerField (min_value = 1 )
@@ -107,11 +138,28 @@ class FeedsRequestSerializer(serializers.Serializer):
107138 ordering = serializers .CharField (max_length = 120 )
108139 verbose = serializers .ChoiceField (choices = ["true" , "false" ])
109140 paginate = serializers .ChoiceField (choices = ["true" , "false" ])
110- format = serializers .ChoiceField (choices = ["csv" , "json" , "txt" ])
141+ format = serializers .ChoiceField (choices = ["csv" , "json" , "txt" , "stix21" ])
142+ asn = serializers .IntegerField (min_value = 1 , required = False , allow_null = True )
143+ min_score = serializers .FloatField (min_value = 0 , max_value = 1 , required = False , allow_null = True )
144+ port = serializers .IntegerField (min_value = 1 , max_value = 65535 , required = False , allow_null = True )
145+ start_date = serializers .DateField (format = "%Y-%m-%d" , required = False , allow_null = True )
146+ end_date = serializers .DateField (format = "%Y-%m-%d" , required = False , allow_null = True )
147+ tag_key = serializers .CharField (max_length = 128 , required = False , allow_blank = True )
148+ tag_value = serializers .CharField (max_length = 256 , required = False , allow_blank = True )
111149
112150 def validate_feed_type (self , feed_type ):
113151 logger .debug (f"FeedsRequestSerializer - validation feed_type: '{ feed_type } '" )
114- return feed_type_validation (feed_type , self .context ["valid_feed_types" ])
152+ feed_types = parse_feed_types (feed_type )
153+ if not feed_types :
154+ raise serializers .ValidationError ("Invalid feed_type: must not be empty" )
155+ valid_feed_types = self .context ["valid_feed_types" ]
156+ if len (feed_types ) > len (valid_feed_types ):
157+ raise serializers .ValidationError (f"Invalid feed_type: too many types specified (max { len (valid_feed_types )} )" )
158+ if "all" in feed_types and len (feed_types ) > 1 :
159+ raise serializers .ValidationError ("Invalid feed_type: 'all' cannot be combined with other feed types" )
160+ for ft in feed_types :
161+ feed_type_validation (ft , valid_feed_types )
162+ return feed_type
115163
116164 def validate_ordering (self , ordering ):
117165 logger .debug (f"FeedsRequestSerializer - validation ordering: '{ ordering } '" )
@@ -122,6 +170,7 @@ class ASNFeedsOrderingSerializer(FeedsRequestSerializer):
122170 ALLOWED_ORDERING_FIELDS = frozenset (
123171 {
124172 "asn" ,
173+ "as_name" ,
125174 "ioc_count" ,
126175 "total_attack_count" ,
127176 "total_interaction_count" ,
@@ -183,6 +232,8 @@ class FeedsResponseSerializer(serializers.Serializer):
183232 login_attempts = serializers .IntegerField (min_value = 0 )
184233 recurrence_probability = serializers .FloatField (min_value = 0 , max_value = 1 )
185234 expected_interactions = serializers .FloatField (min_value = 0 )
235+ attacker_country = serializers .CharField (allow_null = True , allow_blank = True , max_length = 120 )
236+ tags = TagSerializer (many = True , required = False , default = list )
186237
187238 def validate_feed_type (self , feed_type ):
188239 logger .debug (f"FeedsResponseSerializer - validation feed_type: '{ feed_type } '" )
0 commit comments