5151 IMAP_SUCCESS_CONNECTIVITY_TEST ,
5252 IMAP_VALIDATE_INTEGER_MESSAGE ,
5353)
54- from .process_email import ProcessEmail
54+ from .process_email import ProcessEmail , IMAP_APP_ID
5555
5656logger = getLogger ()
5757
@@ -154,6 +154,14 @@ def __init__(self, soar: SOARClient, asset: Asset):
154154 self ._folder_name = None
155155 self ._is_hex = False
156156
157+ def debug_print (self , * args ):
158+ """Debug print for ProcessEmail compatibility"""
159+ logger .debug (" " .join (str (arg ) for arg in args ))
160+
161+ def get_app_id (self ):
162+ """Return IMAP app ID for ProcessEmail compatibility"""
163+ return IMAP_APP_ID
164+
157165 def _get_error_message_from_exception (self , e ):
158166 """Get appropriate error message from the exception"""
159167 error_code = None
@@ -212,19 +220,15 @@ def _connect_to_server(self, first_try=True):
212220 use_ssl = self .asset .use_ssl
213221 server = self .asset .server
214222
215- # Set timeout to avoid stall
216223 socket .setdefaulttimeout (60 )
217224
218- # Connect to the server
219225 try :
220226 if is_oauth or use_ssl :
221227 self ._imap_conn = imaplib .IMAP4_SSL (server )
222228 else :
223229 self ._imap_conn = imaplib .IMAP4 (server )
224- # Try STARTTLS for non-SSL connections
225230 with contextlib .suppress (Exception ):
226231 self ._imap_conn .starttls ()
227- logger .debug ("STARTTLS enabled successfully" )
228232 except Exception as e :
229233 error_text = self ._get_error_message_from_exception (e )
230234 raise Exception (
@@ -235,25 +239,22 @@ def _connect_to_server(self, first_try=True):
235239
236240 logger .info (IMAP_CONNECTED_TO_SERVER )
237241
238- # Login
239242 try :
240243 if is_oauth :
241244 auth_string = self ._generate_oauth_string (
242245 self .asset .username ,
243246 self ._state .get ("oauth_token" , {}).get ("access_token" ),
244247 )
245- result , data = self ._imap_conn .authenticate (
248+ result , _ = self ._imap_conn .authenticate (
246249 "XOAUTH2" , lambda _ : auth_string
247250 )
248251 else :
249- result , data = self ._imap_conn .login (
252+ result , _ = self ._imap_conn .login (
250253 self .asset .username , self .asset .password
251254 )
252255 except Exception as e :
253256 error_text = self ._get_error_message_from_exception (e )
254- # If token is expired, use the refresh token to re-new the access token
255257 if first_try and is_oauth and "Invalid credentials" in error_text :
256- logger .debug ("Try to generate token from refresh token" )
257258 self ._interactive_auth_refresh ()
258259 return self ._connect_to_server (False )
259260 raise Exception (
@@ -263,14 +264,12 @@ def _connect_to_server(self, first_try=True):
263264 ) from None
264265
265266 if result != "OK" :
266- logger .debug (f"Logging in error, result: { result } data: { data } " )
267267 raise Exception (IMAP_ERROR_LOGGING_IN_TO_SERVER )
268268
269269 logger .info (IMAP_LOGGED_IN )
270270
271- # List imap data
272271 try :
273- result , data = self ._imap_conn .list ()
272+ result , _ = self ._imap_conn .list ()
274273 except Exception as e :
275274 error_text = self ._get_error_message_from_exception (e )
276275 raise Exception (
@@ -283,7 +282,7 @@ def _connect_to_server(self, first_try=True):
283282
284283 self ._folder_name = self .asset .folder
285284 try :
286- result , data = self ._imap_conn .select (
285+ result , _ = self ._imap_conn .select (
287286 f'"{ imap_utf7 .encode (self ._folder_name ).decode ()} "' , True
288287 )
289288 except Exception as e :
@@ -296,21 +295,14 @@ def _connect_to_server(self, first_try=True):
296295 ) from e
297296
298297 if result != "OK" :
299- logger .debug (f"Error selecting folder, result: { result } data: { data } " )
300298 raise Exception (
301299 IMAP_ERROR_SELECTING_FOLDER .format (folder = self ._folder_name )
302300 )
303301
304302 logger .info (IMAP_SELECTED_FOLDER .format (folder = self ._folder_name ))
305303
306- no_of_emails = data [0 ]
307- logger .debug (f"Total emails: { no_of_emails } " )
308-
309304 def _get_email_data (self , muuid , folder = None , is_diff = False ):
310305 """Get email data from IMAP server"""
311- email_data = None
312- data_time_info = None
313-
314306 if is_diff and folder :
315307 try :
316308 result , data = self ._imap_conn .select (
@@ -325,17 +317,15 @@ def _get_email_data(self, muuid, folder=None, is_diff=False):
325317 ) from e
326318
327319 if result != "OK" :
328- logger .debug (f"Error selecting folder, result: { result } data: { data } " )
329320 raise Exception (IMAP_ERROR_SELECTING_FOLDER .format (folder = folder ))
330321
331322 logger .info (IMAP_SELECTED_FOLDER .format (folder = folder ))
332323
333- # query for the whole email body
334324 try :
335325 (result , data ) = self ._imap_conn .uid (
336326 "fetch" , muuid , "(INTERNALDATE RFC822)"
337327 )
338- except TypeError : # py3
328+ except TypeError :
339329 (result , data ) = self ._imap_conn .uid (
340330 "fetch" , str (muuid ), "(INTERNALDATE RFC822)"
341331 )
@@ -345,10 +335,6 @@ def _get_email_data(self, muuid, folder=None, is_diff=False):
345335 IMAP_FETCH_ID_FAILED .format (muuid = muuid , excep = error_text )
346336 ) from e
347337
348- logger .debug (
349- f"UID fetch result: { result } , data type: { type (data )} , data: { data } "
350- )
351-
352338 if result != "OK" :
353339 raise Exception (
354340 IMAP_FETCH_ID_FAILED_RESULT .format (
@@ -366,6 +352,7 @@ def _get_email_data(self, muuid, folder=None, is_diff=False):
366352
367353 if not isinstance (data [0 ], tuple ) or len (data [0 ]) < 2 :
368354 raise Exception (f"Invalid data structure for email ID { muuid } : { data [0 ]} " )
355+
369356 try :
370357 email_data = data [0 ][1 ].decode ("UTF-8" )
371358 except UnicodeDecodeError :
@@ -386,28 +373,19 @@ def _get_email_ids_to_process(self, max_emails, lower_id, manner):
386373 raise Exception (f"Failed to get email IDs. Server response: { data } " )
387374
388375 if not data or not data [0 ]:
389- logger .info ("No emails found" )
390376 return []
391377
392378 uids = [int (uid ) for uid in data [0 ].split ()]
393379
394380 if len (uids ) == 1 and uids [0 ] < lower_id :
395- logger .info (f"No new UIDs found (only { uids [0 ]} which is < { lower_id } )" )
396381 return []
397382
398383 uids .sort ()
399-
400384 max_emails = int (max_emails )
401385
402386 if manner == "latest first" :
403- logger .info (
404- f"Getting { max_emails } MOST RECENT email UIDs since UID { lower_id } "
405- )
406- # Return the latest (rightmost) items
407387 return uids [- max_emails :]
408388 else :
409- logger .info (f"Getting NEXT { max_emails } email UIDs since UID { lower_id } " )
410- # Return the oldest (leftmost) items
411389 return uids [:max_emails ]
412390
413391 def _parse_and_create_artifacts (
@@ -423,9 +401,6 @@ def _parse_and_create_artifacts(
423401 dt = parse_data ["dt" ]
424402 dt .replace (tzinfo = tz .tzlocal ())
425403 epoch = int (time .mktime (dt .timetuple ())) * 1000
426- logger .debug (f"Internal date Epoch: { dt } ({ epoch } )" )
427- else :
428- logger .debug (f"Unable to parse date/time: { data_time_info } " )
429404
430405 if config is None :
431406 config = {
@@ -437,35 +412,27 @@ def _parse_and_create_artifacts(
437412 }
438413
439414 process_email = ProcessEmail ()
415+ process_email ._base_connector = self
416+ process_email ._folder_name = self ._folder_name
417+ process_email ._is_hex = self ._is_hex
440418
441- class MockConnector :
442- def __init__ (self , helper ):
443- self .helper = helper
444- self .containers = []
445- self .artifacts = []
446-
447- def save_container (self , container_dict ):
448- self .containers .append (container_dict )
449- return {"id" : 0 }
450-
451- def save_artifact (self , artifact_dict ):
452- self .artifacts .append (artifact_dict )
453- return {"id" : 0 }
454-
455- def send_progress (self , message ):
456- logger .debug (message )
457-
458- def get_config (self ):
459- return config
419+ ret_val , message , results = process_email ._int_process_email (
420+ email_data , email_id , epoch
421+ )
460422
461- mock = MockConnector (self )
462- process_email .process_email (mock , email_data , email_id , config , epoch )
423+ if not ret_val :
424+ logger .error (f"Failed to process email { email_id } : { message } " )
425+ return
463426
464- for container_dict in mock .containers :
465- yield Container (** container_dict )
427+ for result in results :
428+ container_dict = result .get ("container" )
429+ if container_dict :
430+ yield Container (** container_dict )
466431
467- for artifact_dict in mock .artifacts :
468- yield Artifact (** artifact_dict )
432+ artifacts = result .get ("artifacts" , [])
433+ for artifact_dict in artifacts :
434+ if artifact_dict :
435+ yield Artifact (** artifact_dict )
469436
470437
471438@app .test_connectivity ()
@@ -488,70 +455,52 @@ def on_poll(
488455 params : OnPollParams , soar : SOARClient , asset : Asset
489456) -> Iterator [Container | Artifact ]:
490457 """Poll for new emails and ingest as containers/artifacts"""
491-
492458 helper = ImapHelper (soar , asset )
493-
494- # Connect to server
495459 helper ._connect_to_server ()
496460
497- # Access ingestion state for tracking email processing
498461 state = app .actions_manager .ingestion_state
499462
500- # Determine if this is first run
501- is_first_run = state .get ("first_run" , True )
502- lower_id = state .get ("next_muid" , 1 )
463+ is_poll_now = params .container_count is not None
503464
504- # Determine max emails based on first run
505- if is_first_run :
506- max_emails = int ( asset . first_run_max_emails )
465+ if is_poll_now :
466+ lower_id = 1
467+ max_emails = params . container_count if params . container_count > 0 else 100
507468 else :
508- max_emails = int (asset .max_emails )
509-
510- # Allow container_count to override for manual polling
511- if params .container_count :
512- max_emails = params .container_count
513-
514- logger .info (f"Will fetch up to { max_emails } emails (starting from UID { lower_id } )" )
515-
516- # Get email IDs to process
517- try :
518- email_ids = helper ._get_email_ids_to_process (
519- max_emails , lower_id , asset .ingest_manner
469+ is_first_run = state .get ("first_run" , True )
470+ lower_id = state .get ("next_muid" , 1 )
471+ max_emails = (
472+ int (asset .first_run_max_emails ) if is_first_run else int (asset .max_emails )
520473 )
521- logger .info (f"Got { len (email_ids )} email IDs: { email_ids } " )
522- except Exception as e :
523- logger .error (f"Failed to get email IDs: { e } " )
524- logger .exception ("Full traceback:" )
525- raise
474+
475+ email_ids = helper ._get_email_ids_to_process (
476+ max_emails , lower_id , asset .ingest_manner
477+ )
526478
527479 if not email_ids :
528480 logger .info ("No new emails to ingest" )
529481 return
530482
531- logger .info (f"Found { len (email_ids )} emails to ingest" )
532-
533- for i , email_id in enumerate (email_ids ):
534- logger .info (f"Processing email UID { email_id } ({ i + 1 } /{ len (email_ids )} )" )
535-
483+ for email_id in email_ids :
536484 try :
537485 email_data , data_time_info = helper ._get_email_data (email_id )
538- logger .info (f"Got email data, size: { len (email_data )} bytes" )
539486
540487 yield from helper ._parse_and_create_artifacts (
541488 email_id , email_data , data_time_info , asset
542489 )
543490
544491 except Exception as e :
545492 logger .error (f"Error processing email { email_id } : { e } " )
546- logger .exception ("Full traceback:" )
547493 continue
548494
549- if email_ids :
495+ if email_ids and not is_poll_now :
550496 state ["next_muid" ] = int (email_ids [- 1 ]) + 1
551497 state ["first_run" ] = False
552- logger .info (f"Updated next_muid to { state ['next_muid' ]} " )
553498
554- logger .info (f"On_poll completed, processed { len (email_ids )} emails" )
499+
500+ class GetEmailSummary (ActionOutput ):
501+ """Summary output for get_email action"""
502+
503+ container_id : int | None = None
555504
556505
557506class GetEmailParams (Params ):
@@ -778,9 +727,12 @@ def get_email(params: GetEmailParams, soar: SOARClient, asset: Asset) -> GetEmai
778727 soar ._artifacts_api .create (artifact_dict )
779728
780729 message = f"Email ingested with container ID: { container_id } "
730+ soar .set_summary (GetEmailSummary (container_id = container_id ))
781731 else :
782732 message = "Email not ingested."
783733
734+ soar .set_message (message )
735+
784736 ret_val = {"message" : message }
785737 if container_id :
786738 ret_val ["container_id" ] = container_id
0 commit comments