import demistomock as demisto # noqa: F401 from CommonServerPython import * # noqa: F401 """ IMPORTS """ import re import traceback import dateparser import requests import urllib3 EMAIL_ADDRESS_FIELD = "email" EMPLOYEE_ID_FIELD = "employeeid" IS_PROCESSED_FIELD = "isprocessed" DISPLAY_NAME_FIELD = "displayname" LAST_DAY_OF_WORK_FIELD = "lastdayofwork" TERMINATION_DATE_FIELD = "terminationdate" TERMINATION_TRIGGER_FIELD = "terminationtrigger" EMPLOYMENT_STATUS_FIELD = "employmentstatus" PREHIRE_FLAG_FIELD = "prehireflag" REHIRED_EMPLOYEE_FIELD = "rehiredemployee" HIRE_DATE_FIELD = "hiredate" AD_ACCOUNT_STATUS_FIELD = "adaccountstatus" OLD_USER_DATA_FIELD = "olduserdata" SOURCE_PRIORITY_FIELD = "sourcepriority" SOURCE_OF_TRUTH_FIELD = "sourceoftruth" CONVERSION_HIRE_FIELD = "conversionhire" USER_PROFILE_INC_FIELD = "UserProfile" USER_PROFILE_INDICATOR = "User Profile" DATE_FORMAT = "%m/%d/%Y" READ_TIME_OUT_IN_SECONDS = 300 DEFAULT_MAPPER_IN = "IAM Sync User - Workday" BATCH_SIZE = 2000 NEW_HIRE_EVENT_TYPE = "IAM - New Hire" UPDATE_USER_EVENT_TYPE = "IAM - Update User" REHIRE_USER_EVENT_TYPE = "IAM - Rehire User" TERMINATE_USER_EVENT_TYPE = "IAM - Terminate User" ACTIVATE_AD_EVENT_TYPE = "IAM - AD User Activation" DEACTIVATE_AD_EVENT_TYPE = "IAM - AD User Deactivation" DEFAULT_INCIDENT_TYPE = "IAM - Sync User" # Disable insecure warnings urllib3.disable_warnings() class Client(BaseClient): """ Client will implement the service API, should not contain Cortex XSOAR logic. Should do requests and return data """ # Getting Workday Full User Report with a given report URL. This uses RaaS def get_full_report(self, report_url): res = self._http_request(method="GET", full_url=report_url, url_suffix="", timeout=READ_TIME_OUT_IN_SECONDS) return res.get("Report_Entry", []) """ HELPER FUNCTIONS """ def report_to_indicators( report_entries, mapper_in, workday_date_format, deactivation_date_field, days_before_hire_to_sync, days_before_hire_to_enable_ad, source_priority, ): indicators = [] email_to_user_profile: Dict[str, Dict] = {} for entry in report_entries: workday_user = get_workday_user_from_entry(entry, mapper_in, workday_date_format, source_priority) if ( not has_reached_threshold_date(days_before_hire_to_sync, workday_user) or new_hire_email_already_taken(workday_user, None, email_to_user_profile) or is_report_missing_required_user_data(workday_user) or is_termination_event(workday_user, None, deactivation_date_field, first_run=True) ): continue indicator = workday_user_to_indicator(workday_user, days_before_hire_to_enable_ad) indicators.append(indicator) email_to_user_profile[workday_user.get(EMAIL_ADDRESS_FIELD)] = indicator return indicators def workday_user_to_indicator(workday_user, days_before_hire_to_enable_ad): if not has_reached_threshold_date(days_before_hire_to_enable_ad, workday_user): workday_user[AD_ACCOUNT_STATUS_FIELD] = "Pending" raw_json = workday_user.copy() raw_json["value"] = workday_user.get(EMAIL_ADDRESS_FIELD) raw_json["type"] = USER_PROFILE_INDICATOR indicator = { "value": workday_user.get(EMAIL_ADDRESS_FIELD), "type": USER_PROFILE_INDICATOR, "rawJSON": raw_json, "fields": workday_user, } return indicator def convert_incident_fields_to_cli_names(data): converted_data = {} for k, v in data.items(): key_machine_name = re.sub(r"[^a-zA-Z0-9]", "", k).lower() converted_data[key_machine_name] = v return converted_data def reformat_date_fields(user_profile, workday_date_format): for date_field in [TERMINATION_DATE_FIELD, LAST_DAY_OF_WORK_FIELD, HIRE_DATE_FIELD]: if user_profile.get(date_field): datetime_obj = datetime.strptime(user_profile[date_field], workday_date_format) user_profile[date_field] = datetime_obj.strftime(DATE_FORMAT) def get_workday_user_from_entry(entry, mapper_in, workday_date_format, source_priority): workday_user = demisto.mapObject(entry, mapper_in, DEFAULT_INCIDENT_TYPE) workday_user = convert_incident_fields_to_cli_names(workday_user) reformat_date_fields(workday_user, workday_date_format) workday_user[SOURCE_PRIORITY_FIELD] = source_priority workday_user[SOURCE_OF_TRUTH_FIELD] = "Workday IAM" return workday_user def has_reached_threshold_date(num_of_days_before_hire, workday_user): if not num_of_days_before_hire and num_of_days_before_hire != 0: return True hire_date = dateparser.parse(workday_user.get(HIRE_DATE_FIELD)) assert hire_date is not None hire_date = hire_date.date() today = datetime.today().date() delta = (hire_date - today).days if delta > num_of_days_before_hire: demisto.debug( f"Skipped creating an incident for user " f"with email address {workday_user.get(EMAIL_ADDRESS_FIELD)} - " f"the hire date of the employee is in more than {num_of_days_before_hire} days." ) return False return True def new_hire_email_already_taken(workday_user, demisto_user, email_to_user_profile): if demisto_user is None and email_to_user_profile.get(workday_user.get(EMAIL_ADDRESS_FIELD)) is not None: demisto.debug( f"Skipped creating a user profile for the following employee:\n{workday_user}\n\n" f"The user profile doesn't exist but its email is already being used by another user." ) return True return False def is_report_missing_required_user_data(workday_user): if ( not workday_user.get(EMAIL_ADDRESS_FIELD) or not workday_user.get(EMPLOYEE_ID_FIELD) or not workday_user.get(HIRE_DATE_FIELD) ): demisto.debug( f"Skipped creating an incident for the following user profile:\n{workday_user}\n\n" f"The user profile does not contain email address/employee ID/hire date, " f"to fix please add the missing data to the report." ) return True return False def is_tufe_user(demisto_user): if demisto_user is not None and demisto_user.get(TERMINATION_TRIGGER_FIELD) == "TUFE": demisto.debug(f"Dropping event for user with email {demisto_user.get(EMAIL_ADDRESS_FIELD)} as it is a TUFE user.") return True return False def is_event_processed(demisto_user): if demisto_user is not None and demisto_user.get(IS_PROCESSED_FIELD) is True: demisto.debug( f"Dropping event for user with email {demisto_user.get(EMAIL_ADDRESS_FIELD)} as it is currently being processed." ) return True return False def is_termination_event(workday_user, demisto_user, deactivation_date_field, first_run=False): if not first_run and (demisto_user is None or demisto_user.get(AD_ACCOUNT_STATUS_FIELD) == "Disabled"): # skipping termination check - user does not exist or already terminated return False prehire_flag = workday_user.get(PREHIRE_FLAG_FIELD, "").lower() == "true" employment_status = workday_user.get(EMPLOYMENT_STATUS_FIELD, "").lower() if deactivation_date := workday_user.get(deactivation_date_field): deactivation_date = dateparser.parse(workday_user.get(deactivation_date_field)) today = datetime.today() if (employment_status == "terminated" and prehire_flag is False) or (deactivation_date and deactivation_date <= today): demisto.debug(f"A termination event was detected for user with email address {workday_user.get(EMAIL_ADDRESS_FIELD)}.") return True return False def is_display_name_already_taken(demisto_user, workday_user, display_name_to_user_profile): user_display_name = workday_user.get(DISPLAY_NAME_FIELD) demisto_users_by_display_name = display_name_to_user_profile.get(user_display_name) if demisto_users_by_display_name is None: return False if demisto_user is None: demisto.debug( f"Detected a potential new hire for user with email address " f"{workday_user.get(EMAIL_ADDRESS_FIELD)}, but display name is already taken. " f"Please review the incident." ) return True display_name_is_taken_by_another_user = False is_display_name_change = True for user in demisto_users_by_display_name: if user.get(EMPLOYEE_ID_FIELD) == demisto_user.get(EMPLOYEE_ID_FIELD): # user already exists with this display name in XSOAR is_display_name_change = False if ( user.get(EMPLOYEE_ID_FIELD) != demisto_user.get(EMPLOYEE_ID_FIELD) and user.get(AD_ACCOUNT_STATUS_FIELD).lower() != "terminated" ): display_name_is_taken_by_another_user = True if display_name_is_taken_by_another_user and is_display_name_change: demisto.debug( f"Detected an event for user with email address " f"{workday_user.get(EMAIL_ADDRESS_FIELD)}, but its display name is already taken. " f"Please review the incident." ) return True return False def is_new_hire_event(demisto_user, workday_user, deactivation_date_field): if demisto_user is not None: return False prehire_flag = workday_user.get(PREHIRE_FLAG_FIELD, "").lower() == "true" employment_status = workday_user.get(EMPLOYMENT_STATUS_FIELD, "") if prehire_flag is True and not employment_status: demisto.debug(f"A new hire event was detected for user with email address {workday_user.get(EMAIL_ADDRESS_FIELD)}.") return True if deactivation_date := workday_user.get(deactivation_date_field): deactivation_date = dateparser.parse(workday_user.get(deactivation_date_field)) today = datetime.today() if employment_status.lower() != "terminated" and (not deactivation_date or today <= deactivation_date): demisto.debug( f"A non-terminated user with an email address {workday_user.get(EMAIL_ADDRESS_FIELD)} " f"was not found in XSOAR, even though a pre-hire was not detected. Syncing anyway." ) return True return False def is_rehire_event(demisto_user, workday_user, changed_fields): if demisto_user is None or demisto_user.get(AD_ACCOUNT_STATUS_FIELD) != "Disabled": # skipping rehire check - user does not exist or already active/pending return False prehire_flag = workday_user.get(PREHIRE_FLAG_FIELD, "").lower() == "true" is_rehired_employee = workday_user.get(REHIRED_EMPLOYEE_FIELD, "").lower() == "yes" if prehire_flag is True and is_rehired_employee and changed_fields is not None: demisto.debug(f"A rehire event was detected for user with email address {workday_user.get(EMAIL_ADDRESS_FIELD)}.") return True return False def is_ad_activation_event(demisto_user, workday_user, days_before_hire_to_enable_ad): if ( demisto_user and demisto_user.get(AD_ACCOUNT_STATUS_FIELD, "") == "Pending" and has_reached_threshold_date(days_before_hire_to_enable_ad, workday_user) ): demisto.debug( f"An Active Directory activation event was detected for user " f"with email address {workday_user.get(EMAIL_ADDRESS_FIELD)}." ) return True return False def is_ad_deactivation_event(demisto_user, workday_user, days_before_hire_to_enable_ad, source_priority): """ Checks whether the event is IAM - Deactivate User in Active Directory. Note: To avoid misdetection of deactivation events for conversion hires, we check that: 1. The current SOURCE_PRIORITY_FIELD is Workday's - otherwise it's a conversion hire in its first fetch. 2. CONVERSION_HIRE_FIELD is not True - otherwise it's a conversion hire. Args: demisto_user: The user profile in XSOAR. workday_user: Workday user in XSOAR format. days_before_hire_to_enable_ad: Number of days before hire date to enable Active Directory account, `None` if should sync instantly. source_priority: Source priority level. Returns: (bool). True iff the event is an AD deactivation. """ if ( not demisto_user or demisto_user.get(SOURCE_PRIORITY_FIELD) != source_priority or demisto_user.get(CONVERSION_HIRE_FIELD) is True ): return False if demisto_user.get(AD_ACCOUNT_STATUS_FIELD, "") == "Enabled" and not has_reached_threshold_date( days_before_hire_to_enable_ad, workday_user ): demisto.debug( f"An Active Directory deactivation event was detected for user " f"with email address {workday_user.get(EMAIL_ADDRESS_FIELD)}." ) return True return False def is_update_event(workday_user, changed_fields): if changed_fields and workday_user.get(EMPLOYMENT_STATUS_FIELD, "").lower() != "terminated": demisto.debug(f"An update event was detected for user with email address {workday_user.get(EMAIL_ADDRESS_FIELD)}.") return True return False def get_all_user_profiles(): query = f'type:"{USER_PROFILE_INDICATOR}"' display_name_to_user_profile: Dict[str, List[Dict]] = {} employee_id_to_user_profile: Dict[str, Dict] = {} email_to_user_profile: Dict[str, Dict] = {} def handle_batch(user_profiles): for user_profile in user_profiles: user_profile = user_profile.get("CustomFields", {}) display_name = user_profile.get(DISPLAY_NAME_FIELD) employee_id = user_profile.get(EMPLOYEE_ID_FIELD) email = user_profile.get(EMAIL_ADDRESS_FIELD) display_name_to_user_profile.setdefault(display_name, []).append(user_profile) employee_id_to_user_profile[employee_id] = user_profile email_to_user_profile[email] = user_profile search_indicators = IndicatorsSearcher(query=query, size=BATCH_SIZE) for ioc_res in search_indicators: fetched_iocs = ioc_res.get("iocs") or [] handle_batch(fetched_iocs) return display_name_to_user_profile, employee_id_to_user_profile, email_to_user_profile def get_demisto_user(email_to_user_profile, employee_id_to_user_profile, workday_user): demisto_user = None if employee_id := workday_user.get(EMPLOYEE_ID_FIELD): demisto_user = employee_id_to_user_profile.get(employee_id) if not demisto_user and (email := workday_user.get(EMAIL_ADDRESS_FIELD)): demisto_user = email_to_user_profile.get(email) return demisto_user def get_orphan_users(email_to_user_profile, user_emails, source_priority): """Gets all users that don't exist in the Workday report anymore and terminate them in XSOAR.""" events = [] orphan_users = [ email for email, user in email_to_user_profile.items() if email not in user_emails and user.get(EMPLOYMENT_STATUS_FIELD) != "Terminated" and user.get(SOURCE_PRIORITY_FIELD) == source_priority and user.get(IS_PROCESSED_FIELD) is False ] if orphan_users: demisto.debug(f"Found orphan users: {orphan_users}") for email in orphan_users: email_to_user_profile[email][EMPLOYMENT_STATUS_FIELD] = "Terminated" entry = { "Email_Address": email, "UserProfile": email_to_user_profile[email], "Emp_ID": email_to_user_profile[email].get(EMPLOYEE_ID_FIELD), } event = { "name": email, "rawJSON": json.dumps(entry), "type": DEFAULT_INCIDENT_TYPE, "details": "An orphan user was detected (could not find the user in Workday report). " "Please review and terminate if necessary.", } events.append(event) return events def get_profile_changed_fields_str(demisto_user, workday_user): if not demisto_user: return None profile_changed_fields = [] for field, workday_value in workday_user.items(): if (workday_value and not demisto_user.get(field)) or workday_value != demisto_user.get(field): profile_changed_fields.append([field, workday_value]) return "\n".join([f'{field[0]} field was updated to "{field[1]}".' for field in profile_changed_fields]) def is_valid_source_of_truth(demisto_user, source_priority): if demisto_user is not None and demisto_user.get(SOURCE_PRIORITY_FIELD, 1) < source_priority: demisto.debug( f"Skipped creating an incident for user profile {demisto_user.get(EMAIL_ADDRESS_FIELD)}: " f"The user profile in XSOAR has a higher source priority level." ) return False return True def get_event_details( entry, workday_user, demisto_user, days_before_hire_to_sync, days_before_hire_to_enable_ad, deactivation_date_field, display_name_to_user_profile, email_to_user_profile, employee_id_to_user_profile, source_priority, ): """ This function detects the event type and creates a dictionary which holds the event details. If the event should not be created, None is returned. Args: entry: The employee's report entry. workday_user: Workday user in XSOAR format. demisto_user: The user profile in XSOAR. deactivation_date_field: Deactivation date field - "lastdayofwork" or "terminationdate". days_before_hire_to_sync: Number of days before hire date to sync hires, -1 if should sync instantly. days_before_hire_to_enable_ad: Number of days before hire date to enable Active Directory account, -1 if should sync instantly. display_name_to_user_profile: A dictionary that maps display names to user profile indicators in XSOAR. email_to_user_profile: A dictionary that maps email addresses to user profile indicators in XSOAR. employee_id_to_user_profile: A dictionary that maps employee ids to user profile indicators in XSOAR. source_priority: The source priority number. Returns: event: The event details. """ user_email = workday_user.get(EMAIL_ADDRESS_FIELD) changed_fields = get_profile_changed_fields_str(demisto_user, workday_user) demisto.debug(f"{changed_fields=}") # type: ignore if ( not has_reached_threshold_date(days_before_hire_to_sync, workday_user) or new_hire_email_already_taken(workday_user, demisto_user, email_to_user_profile) or is_report_missing_required_user_data(workday_user) or not is_valid_source_of_truth(demisto_user, source_priority) or is_event_processed(demisto_user) ): return None if is_new_hire_event(demisto_user, workday_user, deactivation_date_field): event_type = NEW_HIRE_EVENT_TYPE event_details = "The user has been hired." elif is_ad_activation_event(demisto_user, workday_user, days_before_hire_to_enable_ad): event_type = ACTIVATE_AD_EVENT_TYPE event_details = "Active Directory user account was enabled." elif is_ad_deactivation_event(demisto_user, workday_user, days_before_hire_to_enable_ad, source_priority): event_type = DEACTIVATE_AD_EVENT_TYPE event_details = "Active Directory user account was disabled due to hire date postponement." elif is_rehire_event(demisto_user, workday_user, changed_fields): event_type = REHIRE_USER_EVENT_TYPE event_details = "The user has been rehired." elif is_termination_event(workday_user, demisto_user, deactivation_date_field): event_type = TERMINATE_USER_EVENT_TYPE event_details = "The user has been terminated." elif is_update_event(workday_user, changed_fields): event_type = UPDATE_USER_EVENT_TYPE event_details = f"The user has been updated:\n{changed_fields}" workday_user[OLD_USER_DATA_FIELD] = demisto_user if demisto_user.get(SOURCE_PRIORITY_FIELD) != source_priority: workday_user[CONVERSION_HIRE_FIELD] = True event_details = f"A conversion hire was detected:\n{changed_fields}" else: demisto.debug(f"Could not detect changes in report for user with email address {user_email} - skipping.") return None if is_tufe_user(demisto_user) and event_type != REHIRE_USER_EVENT_TYPE: return None if is_display_name_already_taken(demisto_user, workday_user, display_name_to_user_profile) and event_type in [ NEW_HIRE_EVENT_TYPE, REHIRE_USER_EVENT_TYPE, UPDATE_USER_EVENT_TYPE, ]: event_details = f'Detected an "{event_type}" event, but display name already exists. Please review.' if changed_fields: event_details += f"\n{changed_fields}" event_type = DEFAULT_INCIDENT_TYPE entry[USER_PROFILE_INC_FIELD] = workday_user return {"name": user_email, "rawJSON": json.dumps(entry), "type": event_type, "details": event_details} """ INTEGRATION COMMANDS """ def fetch_samples(client, mapper_in, report_url, workday_date_format): """ This function returns a list of (at most) five sample events (used for classification and mapping only). Args: client: Workday client mapper_in: Incoming mapper's name report_url: The report full URL workday_date_format: Date format in Workday Returns: events: Incidents/events that will be used as samples for classification and mapping. """ events = [] num_of_samples = 5 try: report_entries = client.get_full_report(report_url) num_of_samples = min(num_of_samples, len(report_entries)) report_entries = report_entries[:num_of_samples] for entry in report_entries: workday_user = demisto.mapObject(entry, mapper_in, DEFAULT_INCIDENT_TYPE) workday_user = convert_incident_fields_to_cli_names(workday_user) reformat_date_fields(workday_user, workday_date_format) entry["UserProfile"] = workday_user event = { "name": workday_user.get(EMAIL_ADDRESS_FIELD), "rawJSON": json.dumps(entry), "details": "This is a sample event.", } events.append(event) except Exception as e: demisto.error("Failed to fetch events. Reason: " + str(e)) raise e return events def get_full_report_command(client, mapper_in, report_url, workday_date_format, source_priority): report_entries = client.get_full_report(report_url) outputs = [get_workday_user_from_entry(entry, mapper_in, workday_date_format, source_priority) for entry in report_entries] results = CommandResults(outputs_prefix="WorkdayIAM.ReportEntry", outputs_key_field=EMPLOYEE_ID_FIELD, outputs=outputs) return results def fetch_incidents( client, mapper_in, report_url, workday_date_format, deactivation_date_field, days_before_hire_to_sync, days_before_hire_to_enable_ad, source_priority, ): """ This function will execute each interval (default is 1 minute). Args: client: Workday client mapper_in: Incoming mapper's name report_url: The report full URL. workday_date_format: Date format in Workday report. deactivation_date_field: Deactivation date field - "lastdayofwork" or "terminationdate". days_before_hire_to_sync: Number of days before hire date to sync hires, `None` if should sync instantly. days_before_hire_to_enable_ad: Number of days before hire date to enable Active Directory account, `None` if should sync instantly. source_priority: Source priority level. Returns: events: Incidents/Events that will be created in Cortex XSOAR """ events = [] user_emails = [] try: display_name_to_user_profile, employee_id_to_user_profile, email_to_user_profile = get_all_user_profiles() report_entries = client.get_full_report(report_url) for entry in report_entries: # get the user event (if exists) according to workday report workday_user = get_workday_user_from_entry(entry, mapper_in, workday_date_format, source_priority) demisto_user = get_demisto_user(email_to_user_profile, employee_id_to_user_profile, workday_user) demisto.debug( f'Getting event details for user with email address {workday_user.get("email")}.\n' f'Current user data in XSOAR: {demisto_user=}\nData in Workday: {workday_user=}' ) event = get_event_details( entry, workday_user, demisto_user, days_before_hire_to_sync, days_before_hire_to_enable_ad, deactivation_date_field, display_name_to_user_profile, email_to_user_profile, employee_id_to_user_profile, source_priority, ) if event is not None: events.append(event) # we store all emails of user profiles for later use if demisto_user is not None: user_emails.append(demisto_user.get(EMAIL_ADDRESS_FIELD)) # terminate users in XSOAR which are not on workday report orphan_users_events = get_orphan_users(email_to_user_profile, user_emails, source_priority) events.extend(orphan_users_events) if not events: demisto.info("Did not detect any changes in Workday report.") except Exception as e: demisto.error("Failed to fetch events. Reason: " + str(e)) raise e return events def workday_first_run_command( client, mapper_in, report_url, workday_date_format, deactivation_date_field, days_before_hire_to_sync, days_before_hire_to_enable_ad, source_priority, ): report_entries = client.get_full_report(report_url) indicators = report_to_indicators( report_entries, mapper_in, workday_date_format, deactivation_date_field, days_before_hire_to_sync, days_before_hire_to_enable_ad, source_priority, ) for b in batch(indicators, batch_size=BATCH_SIZE): demisto.createIndicators(b) def test_module( client, is_fetch, report_url, mapper_in, workday_date_format, days_before_hire_to_sync, days_before_hire_to_enable_ad, source_priority, ): """ Returning 'ok' indicates that the integration works like it is supposed to. Connection to the service is successful. Anything else will fail the test. """ # test API connectivity client.get_full_report(report_url) if is_fetch: fetch_incidents( client=client, mapper_in=mapper_in, report_url=report_url, workday_date_format=workday_date_format, days_before_hire_to_sync=days_before_hire_to_sync, days_before_hire_to_enable_ad=days_before_hire_to_enable_ad, deactivation_date_field=TERMINATION_DATE_FIELD, source_priority=source_priority, ) return "ok" def main(): command = demisto.command() params = demisto.params() is_fetch = params.get("isFetch") report_url = params.get("report_url") verify_certificate = not params.get("insecure", False) proxy = params.get("proxy", False) mapper_in = params.get("mapper_in", DEFAULT_MAPPER_IN) workday_username = params.get("credentials", {}).get("identifier") workday_password = params.get("credentials", {}).get("password") workday_date_format = params.get("workday_date_format", DATE_FORMAT) deactivation_date_field = params.get("deactivation_date_field").lower().replace("_", "") source_priority = int(params.get("source_priority", "1")) days_before_hire_to_sync = params.get("days_before_hire_to_sync") if days_before_hire_to_sync: days_before_hire_to_sync = int(days_before_hire_to_sync) days_before_hire_to_enable_ad = params.get("days_before_hire_to_enable_ad") if days_before_hire_to_enable_ad: days_before_hire_to_enable_ad = int(days_before_hire_to_enable_ad) demisto.debug(f"Command being called is {command}") client = Client( base_url="", # using report_url in _http_request verify=verify_certificate, headers={"Accept": "application/json", "Content-Type": "application/json"}, proxy=proxy, ok_codes=(200, 204), auth=requests.auth.HTTPBasicAuth(workday_username, workday_password), ) try: if command == "test-module": return_results( test_module( client, is_fetch, report_url, mapper_in, workday_date_format, days_before_hire_to_sync, days_before_hire_to_enable_ad, source_priority, ) ) if command == "workday-iam-get-full-report": return_results(get_full_report_command(client, mapper_in, report_url, workday_date_format, source_priority)) if command == "fetch-incidents": """ Checks if there are events are stored in the integration context. If yes, it gets it from there. Else, it makes a call to Workday to get a new report Returns the first x events (x being the fetch limit) and stores the remaining in integration context """ last_run = demisto.getLastRun() events = last_run.get("events", []) report_url = params.get("report_url") if params.get("fetch_samples") and not last_run.get("fetched_samples"): sample_events = fetch_samples( client=client, mapper_in=mapper_in, report_url=report_url, workday_date_format=workday_date_format ) demisto.incidents(sample_events) demisto.setLastRun({"fetched_samples": True}) else: if not last_run.get("synced_users") and params.get("first_run"): workday_first_run_command( client=client, mapper_in=mapper_in, report_url=report_url, workday_date_format=workday_date_format, deactivation_date_field=deactivation_date_field, days_before_hire_to_sync=days_before_hire_to_sync, days_before_hire_to_enable_ad=days_before_hire_to_enable_ad, source_priority=source_priority, ) elif not events: # Get the events from Workday by making an API call. Last run is updated only when API call is made events = fetch_incidents( client=client, mapper_in=mapper_in, report_url=report_url, workday_date_format=workday_date_format, deactivation_date_field=deactivation_date_field, days_before_hire_to_sync=days_before_hire_to_sync, days_before_hire_to_enable_ad=days_before_hire_to_enable_ad, source_priority=source_priority, ) fetch_limit = int(params.get("max_fetch")) demisto.incidents(events[:fetch_limit]) demisto.setLastRun({"synced_users": True, "events": events[fetch_limit:]}) except Exception as e: return_error(f"Failed to execute {demisto.command()} command, Error: {e}. Traceback: {traceback.format_exc()}") if __name__ in ["__main__", "builtin", "builtins"]: main()