1111from sqlalchemy .orm import joinedload , defer
1212
1313from strelka_ui .database import db
14- from strelka_ui .models import FileSubmission , User
14+ from strelka_ui .models import FileSubmission , User , get_request_id
1515from strelka_ui .services .auth import auth_required
1616from strelka_ui .services .files import (
1717 decrypt_file ,
1818 check_file_size ,
1919 convert_bytesio_to_filestorage ,
2020)
2121from strelka_ui .services .strelka import get_db_status , get_frontend_status , submit_data
22+ from strelka_ui .services .s3 import upload_file , calculate_expires_at , is_s3_enabled , download_file , is_file_expired
2223from strelka_ui .services .virustotal import (
2324 get_virustotal_positives ,
2425 create_vt_zip_and_download ,
@@ -339,6 +340,21 @@ def get_mimetype_priority(mime_list):
339340 # Get Strelka Submission IoCs
340341 # Store a set of IoCs in IoCs field
341342
343+ # Handle S3 upload if enabled
344+ s3_key = None
345+ s3_expires_at = None
346+ if is_s3_enabled ():
347+ try :
348+ # Create temporary submission ID for S3 key generation
349+ temp_submission_id = get_request_id (response [0 ])
350+ success , s3_key , error_msg = upload_file (file , temp_submission_id )
351+ if success :
352+ s3_expires_at = calculate_expires_at ()
353+ logging .info (f"Successfully uploaded file to S3: { s3_key } " )
354+ else :
355+ logging .warning (f"Failed to upload file to S3: { error_msg } " )
356+ except Exception as e :
357+ logging .error (f"Unexpected error during S3 upload: { e } " )
342358
343359 # Create a new submission object and add it to the database.
344360 new_submission = FileSubmission (
@@ -350,7 +366,9 @@ def get_mimetype_priority(mime_list):
350366 request .headers .get ("User-Agent" ),
351367 user .id ,
352368 submitted_description ,
353- submitted_at
369+ submitted_at ,
370+ s3_key ,
371+ s3_expires_at
354372 )
355373
356374 db .session .add (new_submission )
@@ -708,6 +726,105 @@ def check_vt_api_key():
708726 return jsonify ({"apiKeyAvailable" : api_key_exists }), 200
709727
710728
729+ @strelka .route ("/resubmit/<submission_id>" , methods = ["POST" ])
730+ @auth_required
731+ def resubmit_file (user : User , submission_id : str ) -> Tuple [Response , int ]:
732+ """
733+ Resubmit a file from S3 storage for analysis.
734+
735+ Args:
736+ user: User object representing the authenticated user.
737+ submission_id: ID of the original submission to resubmit.
738+
739+ Returns:
740+ If successful, returns the new submission details and a 200 status code.
741+ If unsuccessful, returns an error message and appropriate status code.
742+ """
743+ if not is_s3_enabled ():
744+ return jsonify ({
745+ "error" : "File resubmission is not enabled" ,
746+ "details" : "S3 storage is not configured or feature is disabled"
747+ }), 400
748+
749+ try :
750+ # Find the original submission
751+ original_submission = db .session .query (FileSubmission ).filter_by (
752+ file_id = submission_id
753+ ).first ()
754+
755+ if not original_submission :
756+ return jsonify ({
757+ "error" : "Original submission not found" ,
758+ "details" : f"Submission { submission_id } does not exist"
759+ }), 404
760+
761+ # Check if file has S3 key
762+ if not original_submission .s3_key :
763+ return jsonify ({
764+ "error" : "File not available for resubmission" ,
765+ "details" : "Original file was not stored in S3"
766+ }), 400
767+
768+ # Check if file has expired
769+ if original_submission .s3_expires_at and is_file_expired (original_submission .s3_expires_at ):
770+ return jsonify ({
771+ "error" : "File has expired" ,
772+ "details" : "The original file has been automatically deleted due to retention policy"
773+ }), 410
774+
775+ # Download file from S3
776+ success , file_storage , error_msg = download_file (original_submission .s3_key )
777+ if not success :
778+ return jsonify ({
779+ "error" : "Failed to retrieve file from storage" ,
780+ "details" : error_msg
781+ }), 500
782+
783+ new_description = f"Resubmission of /submissions/{ original_submission .file_id } "
784+
785+ # Use the existing submit_to_strelka function with resubmission type
786+ # We need to temporarily modify the file object to include the new description
787+ class ResubmissionFile :
788+ def __init__ (self , file_storage , description ):
789+ self .filename = file_storage .filename
790+ self .stream = file_storage .stream
791+ self .content_type = file_storage .content_type
792+ self .description = description
793+
794+ def seek (self , * args , ** kwargs ):
795+ return self .stream .seek (* args , ** kwargs )
796+
797+ def read (self , * args , ** kwargs ):
798+ return self .stream .read (* args , ** kwargs )
799+
800+ resubmission_file = ResubmissionFile (file_storage , new_description )
801+
802+ # Call the existing submit_to_strelka function
803+ response = submit_to_strelka (
804+ resubmission_file ,
805+ user ,
806+ "" , # No submitted_hash for resubmission
807+ new_description ,
808+ "resubmission" # Mark as resubmission type
809+ )
810+
811+ # Add original submission ID to the response
812+ if isinstance (response , tuple ) and len (response ) == 2 :
813+ response_data , status_code = response
814+ if status_code == 200 and hasattr (response_data , 'get_json' ):
815+ json_data = response_data .get_json ()
816+ json_data ["original_submission_id" ] = submission_id
817+ return jsonify (json_data ), status_code
818+
819+ return response
820+
821+ except Exception as e :
822+ logging .error ("Error during file resubmission: %s" , e )
823+ return jsonify ({
824+ "error" : "File resubmission failed" ,
825+ "details" : str (e )
826+ }), 500
827+
711828def submissions_to_json (submission : FileSubmission ) -> Dict [str , any ]:
712829 """
713830 Converts the given submission to a dictionary representation that can be
0 commit comments