Workday IAM
Use the Workday IAM Integration as part of the IAM premium pack.
Identity and Access Management · Workday
Details
| ID | Workday IAM |
|---|---|
| Provider | Workday |
| Category | Identity and Access Management |
| From Version | 6.0.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix XSIAM Cloud Posture Security |
README
Use the Workday IAM Integration as part of the IAM premium pack.
For more information, please refer to the Identity Lifecycle Management article.
Configure Workday IAM in Cortex
| Parameter | Description | Required |
|---|---|---|
| Username | False | |
| Password | False | |
| Workday Report URL | True | |
| Fetch Limit (Recommended less than 200) | False | |
| Fetch incidents | Whether or not to fetch events from Workday report. Enable only when all required configurations are set properly. | False |
| Incident type | False | |
| Trust any certificate (not secure) | False | |
| Use system proxy settings | False | |
| Mapper (incoming) | Used to map Workday report entries to XSOAR indicators format. | False |
| Sync user profiles on first run | If checked, the first fetch won’t trigger incidents but all of the User Profile indicators will be created. | False |
| Fetch Samples | If checked, the fetch incidents command will sample (at most) five incidents. Use only to sample incidents for classification & mapping. | False |
| Date Format in Workday Report | False | |
| Deactivation date field | Select the field that determines when to trigger a termination incident for deactivated employees. | False |
| Number of days before hire date to sync hires | Determines when employees are synced from Workday, i.e., when are the User Profile in XSOAR, and the users in the applications, created. Set to 0 to sync hires on their hire date. Leave empty to sync the hires immediately. | False |
| Number of days before hire date to enable Active Directory account | Determines when to enable the Active Directory accounts of employees. Set to 0 to enable the Active Directory accounts on their hire date. Leave empty to enable the accounts immediately. Note that this is effective only when the employees are already synced to XSOAR, so you should set a number lower, or equal to, the value in the *Number of days before hire date to sync hires* parameter. | False |
| Source Priority Level | Events will be fetched only for User Profiles with a Source Priority value less than or equal to the value of this parameter. | False |
Commands
You can execute these commands from the CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.
workday-iam-get-full-report
Gets the report entries from Workday.
Base Command
workday-iam-get-full-report
Input
There are no input arguments for this command.
Context Output
| Path | Type | Description |
|---|---|---|
| WorkdayIAM.ReportEntry.email | String | Email address of the employee in Workday. |
| WorkdayIAM.ReportEntry.employeeid | String | Employee ID in Workday. |
| WorkdayIAM.ReportEntry.username | String | Username of the employee in Workday. |
| WorkdayIAM.ReportEntry.displayname | String | Display name of the employee. |
| WorkdayIAM.ReportEntry.locationregion | String | Location of the employee in Workday. |
Command Example
!workday-iam-get-full-report
Human Readable Output
Results
| city | costcenter | costcentercode | countryname | department | displayname | employeeid | employeetype | employmentstatus | givenname | hiredate | jobcode | jobfamily | jobfunction | lastdayofwork | leadership | location | locationregion | manageremailaddress | personalemail | prehireflag | rehiredemployee | sourceoftruth | sourcepriority | state | streetaddress | surname | terminationdate | title | username | zipcode | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Santa Clara | Channel Sales | 120100 | United States Of America | Sales NAM:NAM Channel Sales | Ronny Rahardjo | rrahardjo@test.com | 100122 | Regular | Ronny | 03/25/2021 | 2245 | Product Management | Product Management Function | 02/15/2032 | Yes-HQ | Office - USA - CA - Headquarters | Americas | test@test.com | ronnyrahardjo@test.com | True | Yes | Workday IAM | 1 | California | 3000 Tannery Way | Rahardjo | 02/15/2032 | Dir, Product Line Manager | rrahardjo@test.com | 95054 | |
| San Francisco | Magnifier Sales Inc | 101100 | United States Of America | WW Sales Functions:Cortex Sales | Steve Testman | sarnold@test.com | 101351 | Regular | Stephen | 03/26/2021 | 2163 | Software Engineering | Engineering Function | 02/15/2032 | No | Office - USA - CA - San Francisco | Americas | test@test.com | stevearnoldtstc@test.com | True | Yes | Workday IAM | 1 | California | WeWork Embarcadero Center | Arnold | 02/15/2032 | Mgr, SW Engineering | sarnold@test.com | 94111 |
Configuration parameters
credentials— Usernamereport_url— Workday Report URL (required)max_fetch— Fetch Limit (Recommended less than 200)isFetch— Fetch incidentsincidentType— Incident typeincidentFetchInterval— Incidents Fetch Intervalinsecure— Trust any certificate (not secure)proxy— Use system proxy settingsmapper_in— Mapper (incoming)first_run— Sync user profiles on first runfetch_samples— Fetch Samplesworkday_date_format— Date Format in Workday Reportdeactivation_date_field— Deactivation date fielddays_before_hire_to_sync— Number of days before hire date to sync hiresdays_before_hire_to_enable_ad— Number of days before hire date to enable Active Directory accountsource_priority— Source Priority Level
Commands (1)
-
workday-iam-get-full-reportGets the report entries from Workday.
import json import demistomock as demisto # noqa: F401 import pytest from test_data.event_results import events_result from Workday_IAM import ( AD_ACCOUNT_STATUS_FIELD, CONVERSION_HIRE_FIELD, EMAIL_ADDRESS_FIELD, EMPLOYEE_ID_FIELD, EMPLOYMENT_STATUS_FIELD, HIRE_DATE_FIELD, LAST_DAY_OF_WORK_FIELD, PREHIRE_FLAG_FIELD, REHIRE_USER_EVENT_TYPE, REHIRED_EMPLOYEE_FIELD, SOURCE_PRIORITY_FIELD, TERMINATION_TRIGGER_FIELD, Client, fetch_incidents, ) EVENT_RESULTS = events_result def util_load_json(path): with open(path, encoding="utf-8") as f: return json.loads(f.read()) def test_fetch_incidents(mocker): """Unit test Given - raw response of report results When - mock the demisto map object - mock getting demisto indicators Then - validate the incidents values """ client_response = util_load_json("test_data/json_raw_response.json") mapped_user = util_load_json("test_data/mapped_user.json") mocker.patch.object(Client, "get_full_report", return_value=client_response.get("Report_Entry")) mocker.patch("Workday_IAM.get_all_user_profiles", return_value=({}, {}, {})) mocker.patch.object(demisto, "mapObject", return_value=mapped_user) client = Client(base_url="", verify="verify", headers={}, proxy=False, ok_codes=(200, 204), auth=None) fetch_events = fetch_incidents(client, {}, "", "%m/%d/%Y", LAST_DAY_OF_WORK_FIELD, None, None, 1) assert fetch_events == EVENT_RESULTS def test_fetch_incidents_email_change(requests_mock, mocker): """ Given - A workday full report of employees. When - An email address change is detected for the user rrahardj@paloaltonetworks.com. Then - Make sure the IAM - Update User event is returned as expected. """ from test_data.fetch_incidents_email_change_mock_data import ( email_to_user_profile, employee_id_to_user_profile, event_data, full_report, mapped_workday_user, ) requests_mock.get("https://test.com", json=full_report) mocker.patch("Workday_IAM.get_all_user_profiles", return_value=({}, employee_id_to_user_profile, email_to_user_profile)) mocker.patch.object(demisto, "mapObject", return_value=mapped_workday_user) client = Client(base_url="", verify="verify", headers={}, proxy=False, ok_codes=(200, 204), auth=None) fetch_events = fetch_incidents(client, {}, "https://test.com", "%m/%d/%Y", LAST_DAY_OF_WORK_FIELD, None, None, 1) assert fetch_events == event_data def test_fetch_incidents_employee_id_change(requests_mock, mocker): """ Given - A workday full report of employees. When - An employee id change is detected for the user rrahardj@paloaltonetworks.com. Then - Make sure the IAM - Update User event is returned as expected. """ from test_data.fetch_incidents_employee_id_change_mock_data import ( email_to_user_profile, employee_id_to_user_profile, event_data, full_report, mapped_workday_user, ) requests_mock.get("https://test.com", json=full_report) mocker.patch("Workday_IAM.get_all_user_profiles", return_value=({}, employee_id_to_user_profile, email_to_user_profile)) mocker.patch.object(demisto, "mapObject", return_value=mapped_workday_user) client = Client(base_url="", verify="verify", headers={}, proxy=False, ok_codes=(200, 204), auth=None) fetch_events = fetch_incidents(client, {}, "https://test.com", "%m/%d/%Y", LAST_DAY_OF_WORK_FIELD, None, None, 1) assert fetch_events == event_data def test_fetch_incidents_orphan_user(requests_mock, mocker): """ Given - An empty workday report of employees. When - A user profile with email rrahardjo@paloaltonetworks.com exists on XSOAR. Then - Ensure an IAM - Terminate User event is returned for this user. """ from test_data.fetch_incidents_orphan_user_mock_data import email_to_user_profile, event_data, full_report requests_mock.get("https://test.com", json=full_report) mocker.patch("Workday_IAM.get_all_user_profiles", return_value=({}, {}, email_to_user_profile)) client = Client(base_url="", verify="verify", headers={}, proxy=False, ok_codes=(200, 204), auth=None) fetch_events = fetch_incidents(client, {}, "https://test.com", "%m/%d/%Y", LAST_DAY_OF_WORK_FIELD, None, None, 1) assert fetch_events == event_data def test_fetch_incidents_source_priority(requests_mock, mocker): """ Given - A workday full report of employees. When - Workday IAM configured source priority is 2. - A user profile with email rrahardjo@paloaltonetworks.com has a source priority 1. Then - Ensure the event for rrahardjo@paloaltonetworks.com is dropped. """ from test_data.fetch_incidents_source_priority_mock_data import ( email_to_user_profile, employee_id_to_user_profile, event_data, full_report, mapped_workday_user, ) requests_mock.get("https://test.com", json=full_report) mocker.patch.object(demisto, "mapObject", return_value=mapped_workday_user) mocker.patch("Workday_IAM.get_all_user_profiles", return_value=({}, employee_id_to_user_profile, email_to_user_profile)) client = Client(base_url="", verify="verify", headers={}, proxy=False, ok_codes=(200, 204), auth=None) fetch_events = fetch_incidents( client, {}, "https://test.com", "%m/%d/%Y", LAST_DAY_OF_WORK_FIELD, None, None, source_priority=2 ) assert fetch_events == event_data def test_fetch_incidents_partial_name_match(requests_mock, mocker): """ Given - A workday full report of employees. When - A new hire is detected with the same display name as an existing active user. Then - Ensure an "IAM - Sync user" event is detected with the partial name match details. """ from test_data.fetch_incidents_partial_name_match_mock_data import ( display_name_to_user_profile, email_to_user_profile, employee_id_to_user_profile, event_data, full_report, mapped_workday_user, ) requests_mock.get("https://test.com", json=full_report) mocker.patch.object(demisto, "mapObject", return_value=mapped_workday_user) mocker.patch( "Workday_IAM.get_all_user_profiles", return_value=(display_name_to_user_profile, employee_id_to_user_profile, email_to_user_profile), ) mocker.patch("Workday_IAM.get_orphan_users", return_value=[]) # skip the orphan user detection client = Client(base_url="", verify="verify", headers={}, proxy=False, ok_codes=(200, 204), auth=None) fetch_events = fetch_incidents(client, {}, "https://test.com", "%m/%d/%Y", LAST_DAY_OF_WORK_FIELD, None, None, 1) assert fetch_events == event_data @pytest.mark.parametrize( "demisto_user, workday_user, expected_result", [ # a pre-hired employee with no employment status, not synced into XSOAR - should return True (None, {PREHIRE_FLAG_FIELD: "True", EMPLOYMENT_STATUS_FIELD: ""}, True), # non-terminated, active employee, not synced into XSOAR - should return True (None, {EMPLOYMENT_STATUS_FIELD: "active", LAST_DAY_OF_WORK_FIELD: "12/12/2100"}, True), # non-empty demisto_user - should return False ("mocked_non_empty_demisto_user", "mocked_workday_user", False), # non pre-hired, terminated employee - should return False (None, {PREHIRE_FLAG_FIELD: "False", EMPLOYMENT_STATUS_FIELD: "Terminated", LAST_DAY_OF_WORK_FIELD: "12/12/2020"}, False), ], ) def test_is_new_hire_event(demisto_user, workday_user, expected_result): from Workday_IAM import is_new_hire_event assert is_new_hire_event(demisto_user, workday_user, LAST_DAY_OF_WORK_FIELD) == expected_result @pytest.mark.parametrize( "demisto_user, workday_user, expected_result", [ # not a pre-hire, employment status is "terminated" - should return True ( {"mocked_demisto_user": ""}, {PREHIRE_FLAG_FIELD: "False", EMPLOYMENT_STATUS_FIELD: "Terminated", LAST_DAY_OF_WORK_FIELD: "12/12/2100"}, True, ), # non terminated but last day of work is in the past - should return True ({"mocked_demisto_user": ""}, {EMPLOYMENT_STATUS_FIELD: "active", LAST_DAY_OF_WORK_FIELD: "12/12/2020"}, True), # no demisto_user - should return False (None, "mocked_workday_user", False), # demisto_user is already disabled in AD - should return False ({"adaccountstatus": "Disabled"}, "mocked_workday_user", False), # active, non pre-hired user with future last day of work - should return False ( {"mocked_demisto_user": ""}, {PREHIRE_FLAG_FIELD: "False", EMPLOYMENT_STATUS_FIELD: "Active", LAST_DAY_OF_WORK_FIELD: "12/12/2100"}, False, ), # active, pre-hired user with future last day of work - should return False ( {"mocked_demisto_user": ""}, {PREHIRE_FLAG_FIELD: "True", EMPLOYMENT_STATUS_FIELD: "Active", LAST_DAY_OF_WORK_FIELD: "12/12/2100"}, False, ), # pre-hired user with future last day of work - should return False ( {"mocked_demisto_user": ""}, {PREHIRE_FLAG_FIELD: "True", EMPLOYMENT_STATUS_FIELD: "Terminated", LAST_DAY_OF_WORK_FIELD: "12/12/2100"}, False, ), ], ) def test_is_termination_event(demisto_user, workday_user, expected_result): from Workday_IAM import is_termination_event assert is_termination_event(workday_user, demisto_user, LAST_DAY_OF_WORK_FIELD) == expected_result @pytest.mark.parametrize( "demisto_user, workday_user, changed_fields, expected_result", [ # a pre-hired + rehired employee, non empty changed_fields - should return True ( {"adaccountstatus": "Disabled"}, {PREHIRE_FLAG_FIELD: "True", REHIRED_EMPLOYEE_FIELD: "Yes"}, "mocked_changed_fields", True, ), # a pre-hired + rehired employee, no changed_fields (already synced into XSOAR) - should return False ({"adaccountstatus": "Disabled"}, {PREHIRE_FLAG_FIELD: "True", EMPLOYMENT_STATUS_FIELD: ""}, None, False), # no demisto_user - should return False (None, "mocked_workday_user", None, False), # demisto_user AD status is not disabled - should return False ({"adaccountstatus": "Pending"}, "mocked_workday_user", None, False), # non pre-hired / non rehired employee, non empty changed_fields - should return False ( {"adaccountstatus": "Disabled"}, {PREHIRE_FLAG_FIELD: "False", REHIRED_EMPLOYEE_FIELD: "No"}, "mocked_changed_fields", False, ), ], ) def test_is_rehire_event(demisto_user, workday_user, changed_fields, expected_result): from Workday_IAM import is_rehire_event assert is_rehire_event(demisto_user, workday_user, LAST_DAY_OF_WORK_FIELD) == expected_result @pytest.mark.parametrize( "demisto_user, workday_user, days_before_hire_to_enable_ad, expected_result", [ # a pending demisto user, workday user exceeded threshold to enable AD - should return True ({AD_ACCOUNT_STATUS_FIELD: "Pending"}, {HIRE_DATE_FIELD: "12/12/2020"}, 2, True), # a pending demisto user, no threshold to enable AD - should return True ({AD_ACCOUNT_STATUS_FIELD: "Pending"}, {}, None, True), # no demisto_user - should return False (None, "mocked_workday_user", None, False), # did not exceed threshold date - should return False ({AD_ACCOUNT_STATUS_FIELD: "Pending"}, {HIRE_DATE_FIELD: "12/12/2200"}, 2, False), # demisto_user AD status is not pending - should return False ({AD_ACCOUNT_STATUS_FIELD: "Disabled"}, "mocked_workday_user", None, False), ], ) def test_is_ad_activation_event(demisto_user, workday_user, days_before_hire_to_enable_ad, expected_result): from Workday_IAM import is_ad_activation_event assert is_ad_activation_event(demisto_user, workday_user, days_before_hire_to_enable_ad) == expected_result @pytest.mark.parametrize( "demisto_user, workday_user, days_before_hire_to_enable_ad, source_priority, expected_result", [ # source priority field is different than Workday's (conversion hire) - should return False ({AD_ACCOUNT_STATUS_FIELD: "Enabled", SOURCE_PRIORITY_FIELD: 2}, {HIRE_DATE_FIELD: "12/12/2035"}, 2, 1, False), # A conversion hire field is True - should return False ( {AD_ACCOUNT_STATUS_FIELD: "Enabled", CONVERSION_HIRE_FIELD: True, SOURCE_PRIORITY_FIELD: 1}, {HIRE_DATE_FIELD: "12/12/2035"}, 2, 1, False, ), # Active AD account and hire date did not exceed threshold date - should return True ({AD_ACCOUNT_STATUS_FIELD: "Enabled", SOURCE_PRIORITY_FIELD: 1}, {HIRE_DATE_FIELD: "12/12/2035"}, 2, 1, True), # Active AD account, past hire date - should return False ({AD_ACCOUNT_STATUS_FIELD: "Enabled", SOURCE_PRIORITY_FIELD: 1}, {HIRE_DATE_FIELD: "12/12/2020"}, 2, 1, False), # Non active AD account - should return False ({AD_ACCOUNT_STATUS_FIELD: "Pending", SOURCE_PRIORITY_FIELD: 1}, {HIRE_DATE_FIELD: "12/12/2035"}, 2, 1, False), ], ) def test_is_ad_deactivation_event(demisto_user, workday_user, days_before_hire_to_enable_ad, source_priority, expected_result): from Workday_IAM import is_ad_deactivation_event assert is_ad_deactivation_event(demisto_user, workday_user, days_before_hire_to_enable_ad, source_priority) == expected_result @pytest.mark.parametrize( "workday_user, changed_fields, expected_result", [ # a non terminated workday_user with changed fields (hasn't been synced to XSOAR yet) - should return True ({EMPLOYMENT_STATUS_FIELD: "Leave of Absence"}, "mock_changed_fields", True), # a terminated workday_user with changed fields (hasn't been synced to XSOAR yet) - should return False ({EMPLOYMENT_STATUS_FIELD: "Terminated"}, "mock_changed_fields", False), # a non terminated workday_user with no changed fields (already synced to XSOAR) - should return False ({EMPLOYMENT_STATUS_FIELD: "Leave of Absence"}, None, False), ], ) def test_is_update_event(workday_user, changed_fields, expected_result): from Workday_IAM import is_update_event assert is_update_event(workday_user, changed_fields) == expected_result @pytest.mark.parametrize( "demisto_user, expected_result", [(None, False), ({TERMINATION_TRIGGER_FIELD: "TUFE"}, True), ({TERMINATION_TRIGGER_FIELD: "Workday IAM"}, False)], ) def test_is_tufe_user(demisto_user, expected_result): from Workday_IAM import is_tufe_user assert is_tufe_user(demisto_user) == expected_result @pytest.mark.parametrize( "workday_user, demisto_user, expected_event_type", [ # case 1: non-rehired TUFE user ( {EMAIL_ADDRESS_FIELD: "test@example.com", EMPLOYEE_ID_FIELD: "123", HIRE_DATE_FIELD: "02/02/2040"}, { EMAIL_ADDRESS_FIELD: "test@example.com", EMPLOYEE_ID_FIELD: "123", HIRE_DATE_FIELD: "02/02/2040", AD_ACCOUNT_STATUS_FIELD: "Enabled", }, None, ), # case 2: a rehired tufe user ( { EMAIL_ADDRESS_FIELD: "test@example.com", EMPLOYEE_ID_FIELD: "123", HIRE_DATE_FIELD: "mock_date_field", PREHIRE_FLAG_FIELD: "True", REHIRED_EMPLOYEE_FIELD: "Yes", }, { EMAIL_ADDRESS_FIELD: "test@example.com", EMPLOYEE_ID_FIELD: "123", HIRE_DATE_FIELD: "mock_date_field", AD_ACCOUNT_STATUS_FIELD: "Disabled", }, REHIRE_USER_EVENT_TYPE, ), ], ) def test_get_event_details__tufe_user(workday_user, demisto_user, expected_event_type): """ Given - A TUFE user event process. When - Calling get_event_details() method. Then - Ensure None is returned, unless a rehire event is detected. """ from Workday_IAM import get_event_details event = get_event_details( entry={}, workday_user=workday_user, demisto_user=demisto_user, days_before_hire_to_sync=None, days_before_hire_to_enable_ad=None, deactivation_date_field=LAST_DAY_OF_WORK_FIELD, display_name_to_user_profile={}, email_to_user_profile={}, employee_id_to_user_profile={}, source_priority=1, ) event_type = event.get("type") if isinstance(event, dict) else None assert event_type is expected_event_type