Skip to content

Commit 59d56ee

Browse files
committed
chore: testing again again again
1 parent 49c6975 commit 59d56ee

File tree

2 files changed

+65
-103
lines changed

2 files changed

+65
-103
lines changed

src/app.py

Lines changed: 54 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
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

5656
logger = 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

557506
class 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

src/process_email.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,17 @@
2828

2929
from bs4 import BeautifulSoup, UnicodeDammit
3030
from requests.structures import CaseInsensitiveDict
31-
from soar_sdk.shims import phantom
31+
32+
try:
33+
import phantom.app as phantom
34+
except ImportError:
35+
from soar_sdk.shims import phantom
36+
from soar_sdk.shims.phantom.app import APP_SUCCESS, APP_ERROR
37+
38+
phantom.APP_SUCCESS = APP_SUCCESS
39+
phantom.APP_ERROR = APP_ERROR
40+
phantom.is_fail = lambda x: x == APP_ERROR
41+
phantom.is_success = lambda x: x == APP_SUCCESS
3242

3343
from . import phantom_rules
3444
from . import phantom_utils as ph_utils

0 commit comments

Comments
 (0)