import datetime from rest_framework import serializers from django.contrib.auth.models import Group from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned from core.REST_serializers import PublicUserSerializer from core.REST_permissions import group_admin from .models import YaraRule, YaraRuleComment from .services import parse_rule_submission, generate_kwargs_from_parsed_rule class YaraRuleCommentSerializer(serializers.Serializer): content = serializers.CharField() poster = PublicUserSerializer(read_only=True) id = serializers.IntegerField(read_only=True) created = serializers.DateTimeField(read_only=True) modified = serializers.DateTimeField(read_only=True) def retrieve_request_user(self): request = self.context.get("request") request_user = request.user return request_user def create(self, validated_data): # Retrieve rule object identifier from calling view kwargs rule_identifier = self.context['view'].kwargs['rule_pk'] try: rule_object = YaraRule.objects.get(id=rule_identifier) except: raise serializers.ValidationError("Non-existent rule") else: validated_data['rule'] = rule_object # Generate timestamps validated_data['created'] = datetime.datetime.now() validated_data['modified'] = datetime.datetime.now() # Retrieve submitting user validated_data['poster'] = self.retrieve_request_user() return YaraRuleComment.objects.create(**validated_data) def update(self, instance, validated_data): instance.modified = datetime.datetime.now() instance.content = validated_data.get('content', instance.content) instance.save() return instance class YaraRuleStatsSerializer(serializers.Serializer): tag_list = serializers.SerializerMethodField() category_list = serializers.SerializerMethodField() metakey_list = serializers.SerializerMethodField() source_list = serializers.SerializerMethodField() import_list = serializers.SerializerMethodField() scope_list = serializers.SerializerMethodField() submitter_list = serializers.SerializerMethodField() tag_count = serializers.SerializerMethodField() metakey_count = serializers.SerializerMethodField() source_count = serializers.SerializerMethodField() category_count = serializers.SerializerMethodField() active_count = serializers.SerializerMethodField() inactive_count = serializers.SerializerMethodField() pending_count = serializers.SerializerMethodField() rejected_count = serializers.SerializerMethodField() name_conflict_count = serializers.SerializerMethodField() logic_collision_count = serializers.SerializerMethodField() missing_dependency_count = serializers.SerializerMethodField() def __init__(self, *args, **kwargs): # Set which fields should be used at point of initializing fields = kwargs.pop('fields', None) super(YaraRuleStatsSerializer, self).__init__(*args, **kwargs) if fields is not None: allowed = set(fields) existing = set(self.fields.keys()) for field_name in existing - allowed: self.fields.pop(field_name) def get_tag_list(self, obj): return obj.tag_list() def get_category_list(self, obj): return obj.category_list() def get_metakey_list(self, obj): return obj.metakey_list() def get_source_list(self, obj): return obj.source_list() def get_import_list(self, obj): return obj.import_list() def get_scope_list(self, obj): return obj.scope_list() def get_submitter_list(self, obj): return obj.submitter_list() def get_tag_count(self, obj): return obj.tag_count() def get_metakey_count(self, obj): return obj.metakey_count() def get_source_count(self, obj): return obj.source_count() def get_category_count(self, obj): return obj.category_count() def get_active_count(self, obj): return obj.active_count() def get_inactive_count(self, obj): return obj.inactive_count() def get_pending_count(self, obj): return obj.pending_count() def get_rejected_count(self, obj): return obj.rejected_count() def get_name_conflict_count(self, obj): return obj.name_conflict_count() def get_logic_collision_count(self, obj): return obj.logic_collision_count() def get_missing_dependency_count(self, obj): return obj.missing_dependency_count() def to_representation(self, obj): data = super().to_representation(obj) group_context = self.context.get('group_context', None) if group_context: data['source_options'] = group_context.groupmeta.source_options data['category_options'] = group_context.groupmeta.category_options return data class YaraRuleSerializer(serializers.Serializer): STATUS_CHOICES = [entry[0] for entry in YaraRule.STATUS_CHOICES] source = serializers.CharField(required=False) category = serializers.CharField(required=False) rule_content = serializers.CharField(required=False) status = serializers.ChoiceField(required=False, choices=STATUS_CHOICES) def __init__(self, *args, **kwargs): super(YaraRuleSerializer, self).__init__(*args, **kwargs) try: source_blank = not self.retrieve_request_group().groupmeta.source_required self.fields['source'] = serializers.ChoiceField(choices=self.retrieve_sources(), allow_blank=source_blank) category_blank = not self.retrieve_request_group().groupmeta.category_required self.fields['category'] = serializers.ChoiceField(choices=self.retrieve_categories(), allow_blank=category_blank) self.fields['rule_content'].required = True except AttributeError: pass def retrieve_request_user(self): request = self.context.get("request") request_user = request.user return request_user def retrieve_request_group(self): request = self.context.get("request") group_name = request.resolver_match.kwargs.get('group_name') group = Group.objects.get(name=group_name) return group def retrieve_sources(self): return self.retrieve_request_group().groupmeta.source_options def retrieve_categories(self): return self.retrieve_request_group().groupmeta.category_options def get_formatted_rule(self, obj): return obj.format_rule() def get_dependencies(self, obj): dependencies = {'count': 0, 'available': [], 'missing': []} for dependency_name in obj.dependencies: try: dependency_rule = YaraRule.objects.get(name=dependency_name, owner=obj.owner) dependencies['available'].append(dependency_rule.name) # Use to return full dependency content, but we might just do name list for now # dependencies['available'][dependency_rule.name] = dependency_rule.format_rule() except MultipleObjectsReturned: dependency_rule = YaraRule.objects.filter(name=dependency_name, owner=obj.owner)[0] dependencies['available'].append(dependency_rule.name) except ObjectDoesNotExist: dependencies['missing'].append(dependency_name) dependencies['count'] += 1 return dependencies def to_representation(self, obj): return { 'id': obj.id, 'name': obj.name, 'source': obj.source, 'category': obj.category, 'status': obj.status, 'tags': obj.tags, 'imports': obj.imports, 'scopes': obj.scopes, 'metadata': obj.metadata, 'dependencies': self.get_dependencies(obj), 'formatted_rule': self.get_formatted_rule(obj), 'submitter': PublicUserSerializer(obj.submitter).data, 'comments': YaraRuleCommentSerializer(obj.yararulecomment_set.all(), many=True).data, 'created': obj.created, 'modified': obj.modified } def create(self, validated_data): # Retrieve raw yara content to parse out other fields rule_content = validated_data.pop('rule_content') submission_results = parse_rule_submission(rule_content) # Verify parsing was successful if submission_results['parser_error']: raise serializers.ValidationError(submission_results['parser_error']) # Process parsed rule parsed_rules = submission_results['parsed_rules'] # If parsing was successful, generate keyword arguments for rule creation rule_kwargs = generate_kwargs_from_parsed_rule(parsed_rules.pop(0)) rule_kwargs['owner'] = self.retrieve_request_group() rule_kwargs['submitter'] = self.retrieve_request_user() rule_kwargs['created'] = datetime.datetime.now() for attr, value in rule_kwargs.items(): if attr not in ('comments',): validated_data[attr] = value # Ensure status is designated validated_data['status'] = validated_data.get('status', YaraRule.INACTIVE_STATUS) # If guest account, set status to pre-determined value if not group_admin(self.context.get('request')): validated_data['status'] = self.retrieve_request_group().groupmeta.nonprivileged_submission_status # Save the new rule and return as response new_rule = YaraRule(**validated_data) new_rule.save() # Process extracted comments YaraRuleComment.objects.process_extracted_comments(new_rule, rule_kwargs['comments']) return new_rule def update(self, instance, validated_data): instance.status = validated_data.get('status', instance.status) instance.source = validated_data.get('source', instance.source) instance.category = validated_data.get('category', instance.category) instance.modified = datetime.datetime.now() rule_content = validated_data.get('rule_content') # Verify yara content was actually submitted and attempt to parse if rule_content: submission_results = parse_rule_submission(rule_content) if submission_results['parser_error']: raise serializers.ValidationError(submission_results['parser_error']) parsed_rules = submission_results['parsed_rules'] # If parsing was successful, generate keyword arguments for rule updates rule_kwargs = generate_kwargs_from_parsed_rule(parsed_rules.pop(0)) # Update instance attributes from the generated keyword arguments for attr, value in rule_kwargs.items(): # Process extracted comments if attr == 'comments': YaraRuleComment.objects.process_extracted_comments(instance, value) else: # Check for dependency breakage by comparing previous name with new one if (attr == 'name') and (value != instance.name): pass # TO-DO setattr(instance, attr, value) # Save the new instance and return as response instance.save() return instance