get-user-data
This script gathers user data from multiple integrations and returns an Account entity with consolidated information to the context.
python · Aggregated Scripts
Details
| ID | get-user-data |
|---|---|
| Language | python |
| From Version | 6.10.0 |
| Docker Image | demisto/python3:3.12.13.10404775 |
README
This script gathers user data from multiple integrations and returns an Account entity with consolidated information to the context.
Script Data
| Name | Description |
|---|---|
| Script Type | python3 |
| Cortex XSOAR Version | 6.10.0 |
Used In
This script is used in the following playbooks and scripts.
- disable-user
- silent-Suspicious Local User Account Creation
- A user executed multiple LDAP enumeration queries
- silent-A user executed multiple LDAP enumeration queries Test
Inputs
| Argument Name | Description |
|---|---|
| user_id | List of users IDs of the user to retrieve. |
| user_name | List of names of the users to retrieve. |
| user_name | List of names of the users to retrieve. |
| user_email | List of emails of the users to retrieve. |
| attributes | list of AD user’s attributes to retrieve, separated by comma. Relevant only when ‘additional_fields’ argument is set to True. Support for Active Directory v2 only. |
| properties | list of Microsoft user’s properties to retrieve, separated by comma. Relevant only when ‘additional_fields’ argument is set to True. Support for Microsoft Graph User only. |
| domain | The domain to retrieve users from. Available only for the iam-get-user command. |
| brands | Which integrations brands to run the command for. If not provided, the command will run for all available integrations. For multi-select provide a comma-separated list. For example: “SailPointIdentityNow,Active Directory Query v2,PingOne”. |
| verbose | Whether to fetch a human-readable entry for each command or just the final result. If set to true, all commands will have human-readable entries. If set to false, only the final result will be human-readable. |
| additional_fields | Whether to return unmapped fields to the context output under the “AdditionalFields” path. |
| list_non_risky_users | Whether to return only risky users from Core/XDR brands or all given users. If set to true, the execution might take some time. |
Outputs
| Path | Description | Type |
|---|---|---|
| UserData.Brand | The brand (integration) used to disable the user. | String |
| UserData.Instance | The integration instance used to disable the user. | String |
| UserData.ID | The user ID. | String |
| UserData.Username | The username in the relevant system. | String |
| UserData.Email | The email address of the account. | String |
| UserData.RiskLevel | The risk level of the user. | String |
| UserData.AdditionalFields | All the other outputs returned. | String |
| UserData.Status | Status of the results returned from the command. | String |
import demistomock as demisto import pytest from CommonServerPython import * from GetUserData import ( Command, Modules, ad_get_user, aws_iam_get_user, create_user, get_output_key, get_outputs, is_valid_args, main, msgraph_user_get, msgraph_user_get_manager, okta_get_user, prepare_human_readable, run_execute_command, run_list_risky_users_command, run_list_users_command, xdr_and_core_list_all_users, get_data, prisma_cloud_get_user, azure_get_risky_user, azure_list_risky_users, iam_get_user, gsuite_get_user, ) from pytest_mock import MockerFixture def test_modules_init_with_active_brands(): """ Given: A dictionary of modules with some active brands. When: Initializing a Modules instance with this dictionary. Then: The _enabled_brands set should contain only the active brands. """ mock_modules = { "module1": {"brand": "Brand1", "state": "active"}, "module2": {"brand": "Brand2", "state": "inactive"}, "module3": {"brand": "Brand3", "state": "active"}, } modules = Modules(mock_modules, []) assert modules._enabled_brands == {"Brand1", "Brand3"} def test_modules_init_with_no_active_brands(): """ Given: A dictionary of modules with no active brands. When: Initializing a Modules instance with this dictionary. Then: The _enabled_brands set should be empty. """ mock_modules = { "module1": {"brand": "Brand1", "state": "inactive"}, "module2": {"brand": "Brand2", "state": "inactive"}, "module3": {"brand": "Brand3", "state": "inactive"}, } modules = Modules(mock_modules, []) assert modules._enabled_brands == set() def test_is_brand_in_brands_to_run_brand_in_list(): """ Given: A Modules instance with a list of brands to run. When: is_brand_in_brands_to_run is called with a Command for a brand in the list. Then: The method should return True. """ modules = Modules({}, ["Brand1", "Brand2", "Brand3"]) command = Command(brand="Brand2", name="test-command", args={}) result = modules.is_brand_in_brands_to_run(command) assert result is True def test_is_brand_in_brands_to_run_brand_not_in_list(mocker: MockerFixture): """ Given: A Modules instance with a list of brands to run. When: is_brand_in_brands_to_run is called with a Command for a brand not in the list. Then: The method should return False and log a debug message. """ modules = Modules({}, ["Brand1", "Brand2", "Brand3"]) command = Command(brand="Brand4", name="test-command", args={}) mock_debug = mocker.patch.object(demisto, "debug") result = modules.is_brand_in_brands_to_run(command) assert result is False mock_debug.assert_called_once_with( "Skipping command 'test-command' since the brand 'Brand4' is not in the list of brands to run." ) def test_is_brand_in_brands_to_run_empty_brands_list(): """ Given: A Modules instance with an empty list of brands to run. When: is_brand_in_brands_to_run is called with any Command. Then: The method should return True. """ modules = Modules({}, []) command = Command(brand="AnyBrand", name="test-command", args={}) result = modules.is_brand_in_brands_to_run(command) assert result is True def test_is_brand_available_brand_enabled_and_in_brands_to_run(mocker: MockerFixture): """ Given: A Modules instance with an enabled brand that is in the brands to run. When: is_brand_available is called with a Command for that brand. Then: The method should return True. """ mock_modules = {"module1": {"brand": "TestBrand", "state": "active"}} modules = Modules(mock_modules, ["TestBrand"]) mocker.patch.object(modules, "is_brand_in_brands_to_run", return_value=True) command = Command(brand="TestBrand", name="test-command", args={}) result = modules.is_brand_available(command) assert result is True def test_is_brand_available_brand_not_enabled(mocker: MockerFixture): """ Given: A Modules instance with a brand that is not enabled. When: is_brand_available is called with a Command for that brand. Then: The method should return False and log a debug message. """ mock_modules = {"module1": {"brand": "TestBrand", "state": "inactive"}} modules = Modules(mock_modules, []) mock_debug = mocker.patch.object(demisto, "debug") command = Command(brand="TestBrand", name="test-command", args={}) result = modules.is_brand_available(command) assert result is False mock_debug.assert_called_once_with("Skipping command 'test-command' since the brand 'TestBrand' is not available.") def test_is_brand_available_brand_not_in_brands_to_run(mocker: MockerFixture): """ Given: A Modules instance with an enabled brand that is not in the brands to run. When: is_brand_available is called with a Command for that brand. Then: The method should return False. """ mock_modules = {"module1": {"brand": "TestBrand", "state": "active"}} modules = Modules(mock_modules, ["OtherBrand"]) mocker.patch.object(modules, "is_brand_in_brands_to_run", return_value=False) command = Command(brand="TestBrand", name="test-command", args={}) result = modules.is_brand_available(command) assert result is False def test_is_valid_args_with_valid_args(): """ Given: A Command object with valid arguments. When: is_valid_args is called with this Command object. Then: The function should return True. """ command = Command(brand="TestBrand", name="test-command", args={"user_id": "123"}) result = is_valid_args(command) assert result is True def test_is_valid_args_with_empty_args(): """ Given: A Command object with empty arguments. When: is_valid_args is called with this Command object. Then: The function should return True (as per the implementation). """ command = Command(brand="TestBrand", name="test-command", args={}) result = is_valid_args(command) assert result is True def test_is_valid_args_with_none_args(): """ Given: A Command object with None as arguments. When: is_valid_args is called with this Command object. Then: The function should return True (as per the implementation). """ command = Command(brand="TestBrand", name="test-command", args=None) result = is_valid_args(command) assert result is True def test_is_valid_args_with_all_empty_values(mocker: MockerFixture): """ Given: A Command object with arguments that all have empty values. When: is_valid_args is called with this Command object. Then: The function should return False and log a debug message. """ command = Command( brand="TestBrand", name="test-command", args={"user_id": "", "user_name": "", "user_email": ""}, ) mock_debug = mocker.patch.object(demisto, "debug") result = is_valid_args(command) assert result is False mock_debug.assert_called_once_with("Skipping command 'test-command' since no required arguments were provided.") def test_create_user_with_minimal_info(): """ Given: Minimal user information (source and username). When: The create_user function is called. Then: It should return a dictionary with the provided information and source. """ # Arrange source = "TestSource" username = "testuser" # Act result = create_user(source=source, username=username) # Assert assert result == {"Source": "TestSource", "Brand": "TestSource", "Username": "testuser", "Instance": None} def test_create_user_with_additional_fields(): """ Given: Additional fields are provided for a user. When: create_user is called with these fields. Then: It should return a dictionary with additional_fields key. """ user_info = { "source": "FullTestSource", "id": "123", "username": "fulluser", "display_name": "Full User", "email_address": "full@test.com", "job_title": "Manager", "manager_email": "manager@test.com", "manager_display_name": "Manager Name", "risk_level": "Low", } result = create_user(**user_info, additional_fields=True) expected = { "AdditionalFields": { "display_name": "Full User", "job_title": "Manager", "manager_display_name": "Manager Name", "manager_email": "manager@test.com", }, "Email": "full@test.com", "ID": "123", "RiskLevel": "Low", "Source": "FullTestSource", "Brand": "FullTestSource", "Username": "fulluser", "Instance": None, } assert result == expected def test_create_user_without_additional_fields(): """ Given: Additional fields are provided for a user with additional_fields arg set to false. When: create_user is called with these fields. Then: It should return a dictionary with main keys only. """ user_info = { "source": "FullTestSource", "id": "123", "username": "fulluser", "display_name": "Full User", "email_address": "full@test.com", "job_title": "Manager", "manager_email": "manager@test.com", "manager_display_name": "Manager Name", "risk_level": "Low", } result = create_user(**user_info, additional_fields=False) expected = { "Email": "full@test.com", "ID": "123", "RiskLevel": "Low", "Source": "FullTestSource", "Brand": "FullTestSource", "Username": "fulluser", "Instance": None, } assert result == expected def test_create_user_with_single_item_list(): """ Given: A field is provided as a single-item list. When: create_user is called with this field. Then: It should return a dictionary with the field value extracted from the list. """ source = "SingleListSource" username = "listuser" groups = ["singlegroup"] result = create_user(source=source, username=username, groups=groups, additional_fields=True) assert result == { "AdditionalFields": {"groups": ["singlegroup"]}, "Source": "SingleListSource", "Brand": "SingleListSource", "Username": "listuser", "Instance": None, } def test_create_user_with_empty_fields(): """ Given: All fields are provided as None or empty lists. When: create_user is called with these fields. Then: It should return an empty dictionary. """ user = create_user( source="EmptyFieldsSource", id=None, username=None, display_name=None, email_address=None, groups=[], type=None, job_title=None, office=None, telephone_number=None, is_enabled=None, manager_email=None, manager_display_name=None, risk_level=None, additional_fields=True, ) assert user == {"Source": "EmptyFieldsSource", "Brand": "EmptyFieldsSource", "Instance": None} def test_prepare_human_readable_success(): """ Given: A command name, arguments, and human-readable output for a successful command execution. When: prepare_human_readable is called with these inputs. Then: It should return a list with a single CommandResults object containing the formatted output. """ command_name = "test-command" args = {"arg1": "value1", "arg2": {"nested": "value2"}} human_readable = "Test output" result = prepare_human_readable(command_name, args, human_readable) assert len(result) == 1 assert isinstance(result[0], CommandResults) assert ( result[0].readable_output == '#### Result for !test-command arg1="value1" arg2="{\\\\"nested\\\\": \\\\"value2\\\\"}"\nTest output' ) assert result[0].mark_as_note is True def test_prepare_human_readable_error(): """ Given: A command name, arguments, and human-readable output for a command execution that resulted in an error. When: prepare_human_readable is called with these inputs and is_error set to True. Then: It should return a list with a single CommandResults object containing the formatted error output. """ command_name = "test-command" args = {"arg1": "value1"} human_readable = "Error occurred" result = prepare_human_readable(command_name, args, human_readable, is_error=True) assert len(result) == 1 assert isinstance(result[0], CommandResults) assert result[0].readable_output == '#### Error for !test-command arg1="value1"\nError occurred' assert result[0].entry_type == EntryType.ERROR assert result[0].mark_as_note is True def test_prepare_human_readable_empty_output(): """ Given: A command name and arguments, but an empty human-readable output. When: prepare_human_readable is called with these inputs. Then: It should return an empty list. """ command_name = "test-command" args = {"arg1": "value1"} human_readable = "" result = prepare_human_readable(command_name, args, human_readable) assert result == [] def test_prepare_human_readable_empty_args(): """ Given: A command name, empty arguments, and human-readable output. When: prepare_human_readable is called with these inputs. Then: It should return a list with a single CommandResults object containing the formatted output without arguments. """ command_name = "test-command" args = {} human_readable = "Test output" result = prepare_human_readable(command_name, args, human_readable) assert len(result) == 1 assert isinstance(result[0], CommandResults) assert result[0].readable_output == "#### Result for !test-command \nTest output" assert result[0].mark_as_note is True def test_get_output_key_exact_match(): """ Given: A raw_context dictionary with an exact match for the output_key. When: get_output_key is called with the matching output_key. Then: The function should return the exact matching key. """ raw_context = {"Account": {"Username": "john.doe"}} result = get_output_key("Account", raw_context) assert result == "Account" def test_get_output_key_partial_match(): """ Given: A raw_context dictionary with a key that starts with the output_key followed by a parenthesis. When: get_output_key is called with the partial matching output_key. Then: The function should return the full key that starts with the output_key. """ raw_context = {"Account(val.ID == obj.ID)": [{"Username": "john.doe"}]} result = get_output_key("Account", raw_context) assert result == "Account(val.ID == obj.ID)" def test_get_output_key_no_match(mocker: MockerFixture): """ Given: A raw_context dictionary without any key matching the output_key. When: get_output_key is called with a non-matching output_key. Then: The function should return an empty string and log a debug message. """ raw_context = {"User": {"Name": "John Doe"}} mock_debug = mocker.patch.object(demisto, "debug") result = get_output_key("Account", raw_context) assert result == "" mock_debug.assert_called_once_with("Output key Account not found in entry context keys: ['User']") def test_get_outputs_with_single_item(): """ Given: A raw_context dictionary with a single item for the output key. When: get_outputs is called with the output key. Then: It should return the single item as a dictionary. """ raw_context = { "Account(val.Username == obj.Username)": { "Username": "john.doe", "Email": "john.doe@example.com", "DisplayName": "John Doe", } } output_key = "Account(val.Username == obj.Username)" result = get_outputs(output_key, raw_context) assert result == { "Username": "john.doe", "Email": "john.doe@example.com", "DisplayName": "John Doe", } def test_get_outputs_with_list(): """ Given: A raw_context dictionary with a list for the output key. When: get_outputs is called with the output key. Then: It should return the first item of the list as a dictionary. """ raw_context = { "Account(val.Username == obj.Username)": [ { "Username": "john.doe", "Email": "john.doe@example.com", "DisplayName": "John Doe", }, { "Username": "jane.doe", "Email": "jane.doe@example.com", "DisplayName": "Jane Doe", }, ] } output_key = "Account(val.Username == obj.Username)" result = get_outputs(output_key, raw_context) assert result == { "Username": "john.doe", "Email": "john.doe@example.com", "DisplayName": "John Doe", } def test_get_outputs_with_empty_context(): """ Given: An empty raw_context dictionary. When: get_outputs is called with any output key. Then: It should return an empty dictionary. """ raw_context = {} output_key = "Account(val.Username == obj.Username)" result = get_outputs(output_key, raw_context) assert result == {} def test_get_outputs_with_missing_key(): """ Given: A raw_context dictionary without the specified output key. When: get_outputs is called with a non-existent output key. Then: It should return an empty dictionary. """ raw_context = {"OtherKey": {"SomeData": "Value"}} output_key = "Account(val.Username == obj.Username)" result = get_outputs(output_key, raw_context) assert result == {} def test_run_execute_command_success(mocker: MockerFixture): """ Given: A command name and arguments for a successful command execution. When: The run_execute_command function is called. Then: It should return the expected entry context, human readable output, and an empty list of errors. """ # Mock the necessary Demisto functions mocker.patch.object(demisto, "debug") mocker.patch.object( demisto, "executeCommand", return_value=[ { "Type": 1, "Contents": {"some": "data"}, "ContentsFormat": "json", "HumanReadable": "Command executed successfully", "EntryContext": {"ContextKey": "ContextValue"}, "ModuleName": "inst1", } ], ) # Call the function entry_context, human_readable, errors = run_execute_command("test-command", {"arg1": "value1"}) # Assert the results assert entry_context == [{"ContextKey": "ContextValue", "instance": "inst1"}] assert human_readable == "Command executed successfully" assert errors == [] demisto.debug.assert_called_with("Finished executing command: test-command") def test_run_execute_command_error(mocker: MockerFixture): """ Given: A command name and arguments for a command execution that results in an error. When: The run_execute_command function is called. Then: It should return an empty entry context, empty human readable output, and a list containing a CommandResults object with the error. """ # Mock the necessary Demisto functions mocker.patch.object(demisto, "debug") mocker.patch.object( demisto, "executeCommand", return_value=[ { "Type": 4, "Contents": "Error occurred", "ContentsFormat": "text", "ModuleName": "inst1", } ], ) # Mock the is_error and get_error functions mocker.patch("GetUserData.is_error", return_value=True) mocker.patch("GetUserData.get_error", return_value="Error occurred") # Mock the prepare_human_readable function mock_prepare_human_readable = mocker.patch( "GetUserData.prepare_human_readable", return_value=[mocker.Mock(spec=CommandResults)], ) # Call the function entry_context, human_readable, errors = run_execute_command("test-command", {"arg1": "value1"}) # Assert the results assert entry_context == [{"instance": "inst1"}] assert human_readable == "" assert len(errors) == 1 assert isinstance(errors[0], CommandResults) mock_prepare_human_readable.assert_called_once_with("test-command", {"arg1": "value1"}, "Error occurred", is_error=True) demisto.debug.assert_called_with("Finished executing command: test-command") def test_run_execute_command_multiple_entries(mocker: MockerFixture): """ Given: A command name and arguments for a command execution that returns multiple entries. When: The run_execute_command function is called. Then: It should return a list of entry contexts, concatenated human readable output, and an empty list of errors. """ # Mock the necessary Demisto functions mocker.patch.object(demisto, "debug") mocker.patch.object( demisto, "executeCommand", return_value=[ { "Type": 1, "Contents": {"data1": "value1"}, "ContentsFormat": "json", "HumanReadable": "First entry", "EntryContext": {"Context1": "Value1"}, "ModuleName": "inst1", }, { "Type": 1, "Contents": {"data2": "value2"}, "ContentsFormat": "json", "HumanReadable": "Second entry", "EntryContext": {"Context2": "Value2"}, "ModuleName": "inst2", }, ], ) # Call the function entry_context, human_readable, errors = run_execute_command("test-command", {"arg1": "value1"}) # Assert the results assert entry_context == [ { "Context1": "Value1", "instance": "inst1", }, { "Context2": "Value2", "instance": "inst2", }, ] assert human_readable == "First entry\nSecond entry" assert errors == [] demisto.debug.assert_called_with("Finished executing command: test-command") class TestGetUserData: def test_ad_get_user(self, mocker: MockerFixture): """ Given: A Command object for ad_get_user. When: The function is called with the Command object. Then: It returns the expected tuple of readable outputs, account output, and manager DN. """ command = Command("Active Directory Query v2", "ad-get-user", {"username": "ad_user"}) mock_outputs = { "name": ["ad_user"], "sAMAccountName": ["ad_user_sam"], "displayName": ["AD User"], "mail": ["ad_user@example.com"], "memberOf": ["Group1", "Group2"], "userAccountControlFields": {"ACCOUNTDISABLE": False}, "manager": ["CN=Manager,OU=Users,DC=example,DC=com"], } expected_user = [ { "AdditionalFields": { "displayName": "AD User", "manager": "CN=Manager,OU=Users,DC=example,DC=com", "memberOf": ["Group1", "Group2"], "name": "ad_user", "userAccountControlFields": {"ACCOUNTDISABLE": False}, }, "Email": "ad_user@example.com", "Source": "Active Directory Query v2", "Brand": "Active Directory Query v2", "Username": "ad_user_sam", "Instance": None, } ] mocker.patch( "GetUserData.run_execute_command", return_value=([mock_outputs], "Human readable output", []), ) mocker.patch("GetUserData.get_output_key", return_value="ActiveDirectory.Users") mocker.patch("GetUserData.get_outputs", return_value=mock_outputs) mocker.patch("GetUserData.prepare_human_readable", return_value=[]) result = ad_get_user(command, additional_fields=True) assert isinstance(result, tuple) assert len(result) == 2 assert isinstance(result[0], list) assert result[1] == expected_user def test_ad_get_user_attributes(self, mocker: MockerFixture): """ Given: A Command object for ad_get_user. When: The function is called with the Command object and attributes. Then: It returns the expected tuple of readable outputs, account output, and manager DN. """ command = Command("Active Directory Query v2", "ad-get-user", {"username": "ad_user", "attributes": "whenCreated"}) mock_outputs = { "name": "ad_user", "displayName": "AD User", "mail": "ad_user@example.com", "memberOf": ["Group1"], "userAccountControlFields": {"ACCOUNTDISABLE": False}, "manager": ["CN=Manager,OU=Users,DC=example,DC=com"], "whenCreated": ["2024-11-05 09:11:18+00:00"], } expected_account = [ { "AdditionalFields": { "displayName": "AD User", "manager": "CN=Manager,OU=Users,DC=example,DC=com", "memberOf": "Group1", "name": "ad_user", "userAccountControlFields": {"ACCOUNTDISABLE": False}, "whenCreated": "2024-11-05 09:11:18+00:00", }, "Email": "ad_user@example.com", "Source": "Active Directory Query v2", "Brand": "Active Directory Query v2", "Instance": None, } ] mocker.patch( "GetUserData.run_execute_command", return_value=([mock_outputs], "Human readable output", []), ) mocker.patch("GetUserData.get_output_key", return_value="ActiveDirectory.Users") mocker.patch("GetUserData.get_outputs", return_value=mock_outputs) mocker.patch("GetUserData.prepare_human_readable", return_value=[]) result = ad_get_user(command, additional_fields=True) assert isinstance(result, tuple) assert len(result) == 2 assert isinstance(result[0], list) assert result[1] == expected_account def test_okta_get_user(self, mocker: MockerFixture): """ Given: A Command object for okta_get_user. When: The function is called with the Command object. Then: It returns the expected tuple of readable outputs and account output. """ command = Command("Okta v2", "okta-get-user", {"userId": "101112"}) mock_outputs = { "ID": "101112", "Username": "okta_user", "DisplayName": "Okta User", "Email": "okta@example.com", "Status": "ACTIVE", "Manager": "Okta Manager", } expected_account = [ { "AdditionalFields": {"DisplayName": "Okta User", "Manager": "Okta Manager", "Status": "ACTIVE"}, "Email": "okta@example.com", "ID": "101112", "Source": "Okta v2", "Brand": "Okta v2", "Username": "okta_user", "Instance": None, } ] mocker.patch( "GetUserData.run_execute_command", return_value=([mock_outputs], "Human readable output", []), ) mocker.patch("GetUserData.get_output_key", return_value="Account") mocker.patch("GetUserData.get_outputs", return_value=mock_outputs) mocker.patch("GetUserData.prepare_human_readable", return_value=[]) result = okta_get_user(command, additional_fields=True) assert isinstance(result, tuple) assert len(result) == 2 assert isinstance(result[0], list) assert result[1] == expected_account def test_aws_iam_get_user(self, mocker: MockerFixture): """ Given: A Command object for aws_iam_get_user. When: The function is called with the Command object. Then: It returns the expected tuple of readable outputs and account output. """ command = Command("AWS - IAM", "aws-iam-get-user", {"userName": "aws_user"}) mock_outputs = {"UserId": "AIDAXXXXXXXXXXXXXXXX", "UserName": "aws_user"} expected_account = [ {"ID": "AIDAXXXXXXXXXXXXXXXX", "Source": "AWS - IAM", "Brand": "AWS - IAM", "Username": "aws_user", "Instance": None} ] mocker.patch( "GetUserData.run_execute_command", return_value=([mock_outputs], "Human readable output", []), ) mocker.patch("GetUserData.get_output_key", return_value="AWS.IAM.Users") mocker.patch("GetUserData.get_outputs", return_value=mock_outputs) mocker.patch("GetUserData.prepare_human_readable", return_value=[]) result = aws_iam_get_user(command, additional_fields=True) assert isinstance(result, tuple) assert len(result) == 2 assert isinstance(result[0], list) assert result[1] == expected_account def test_msgraph_user_get(self, mocker: MockerFixture): """ Given: A Command object for msgraph_user_get. When: The function is called with the Command object. Then: It returns the expected tuple of readable outputs and account output. """ command = Command("Microsoft Graph User", "msgraph-user-get", {"user": "graph_user"}) mock_outputs = { "ID": "131415", "Username": "graph_user", "DisplayName": "Graph User", "Email": {"Address": "graph@example.com"}, "JobTitle": "Developer", "Office": "HQ", "TelephoneNumber": "123-456-7890", "Type": "Member", } expected_account = [ { "AdditionalFields": { "DisplayName": "Graph User", "JobTitle": "Developer", "Office": "HQ", "TelephoneNumber": "123-456-7890", "Type": "Member", }, "Email": "graph@example.com", "ID": "131415", "Source": "Microsoft Graph User", "Brand": "Microsoft Graph User", "Username": "graph_user", "Instance": None, } ] mocker.patch( "GetUserData.run_execute_command", return_value=([mock_outputs], "Human readable output", []), ) mocker.patch("GetUserData.get_output_key", return_value="Account") mocker.patch("GetUserData.get_outputs", return_value=mock_outputs) mocker.patch("GetUserData.prepare_human_readable", return_value=[]) result = msgraph_user_get(command, additional_fields=True) assert isinstance(result, tuple) assert len(result) == 2 assert isinstance(result[0], list) assert result[1] == expected_account def test_msgraph_user_with_properties(self, mocker: MockerFixture): """ Given: msgraph_user_get is called and demisto.args() contains a 'properties' key. When: The function runs and modifies command.args. Then: The 'properties' key from demisto.args() is injected into command.args and passed to run_execute_command. """ mocker.patch("GetUserData.demisto.args", return_value={"properties": "DisplayName,JobTitle"}) command = Command("Microsoft Graph User", "msgraph-user-get", {"user": "graph_user"}) mocker_run = mocker.patch("GetUserData.run_execute_command", return_value=([{}], "Human readable", [])) mocker.patch("GetUserData.get_output_key", return_value="Account") mocker.patch("GetUserData.get_outputs", return_value={}) mocker.patch("GetUserData.prepare_human_readable", return_value=[]) msgraph_user_get(command, additional_fields=False) mocker_run.assert_called_once() called_name, called_args = mocker_run.call_args[0] assert called_args["properties"] == "DisplayName,JobTitle,ID,Mail" def test_msgraph_user_get_manager(self, mocker: MockerFixture): """ Given: A Command object for msgraph_user_get_manager. When: The function is called with the Command object. Then: It returns the expected tuple of readable outputs and account output. """ command = Command("Microsoft Graph User", "msgraph-user-get-manager", {"user": "graph_user"}) mock_outputs = {"Manager": {"DisplayName": "Graph Manager", "Mail": "manager@example.com"}} expected_account = {"ManagerDisplayName": "Graph Manager", "ManagerEmail": "manager@example.com"} mocker.patch( "GetUserData.run_execute_command", return_value=([mock_outputs], "Human readable output", []), ) mocker.patch("GetUserData.get_output_key", return_value="MSGraphUserManager") mocker.patch("GetUserData.get_outputs", return_value=mock_outputs) mocker.patch("GetUserData.prepare_human_readable", return_value=[]) result = msgraph_user_get_manager(command) assert isinstance(result, dict) assert result == expected_account def test_azure_list_risky_users(self, mocker: MockerFixture): """ Given: A Command object for azure_list_risky_users with a user ID (not email). When: The function is called with the Command object. Then: It returns the expected tuple of readable outputs and account output. """ user_id = "azure_user_id" command = Command("Azure Risky Users", "azure-risky-user-get", {"id": user_id}) mock_response = { "AzureRiskyUsers.RiskyUser": { "id": "azure_user_id", "userDisplayName": "Azure User", "userPrincipalName": "azure.user@example.com", "riskLevel": "HIGH", } } mocker.patch( "GetUserData.run_execute_command", return_value=([mock_response], "Human readable output", []), ) mocker.patch("GetUserData.get_output_key", return_value="AzureRiskyUsers.RiskyUser") mocker.patch("GetUserData.prepare_human_readable", return_value=[]) result = azure_get_risky_user(command, additional_fields=True) assert isinstance(result, tuple) assert len(result) == 2 assert isinstance(result[0], list) assert len(result[1]) == 1 assert result[1][0]["ID"] == "azure_user_id" assert result[1][0]["Username"] == "Azure User" assert result[1][0]["Email"] == "azure.user@example.com" assert result[1][0]["RiskLevel"] == "HIGH" def test_azure_list_risky_users_with_email(self, mocker: MockerFixture): """ Given: A Command object for azure_list_risky_users with an email address to filter. When: The function is called with the Command object containing filter_email. Then: It filters by userPrincipalName and returns the matching user. """ user_email = "test.user@example.com" command = Command("AzureRiskyUsers", "azure-risky-users-list", {"filter_email": user_email}) mock_list_response = { "AzureRiskyUsers.RiskyUser": [ { "id": "user-id-123", "userPrincipalName": "test.user@example.com", "userDisplayName": "Test User", "riskLevel": "high", } ] } mocker.patch( "GetUserData.run_execute_command", return_value=([mock_list_response], "Human readable output", []), ) mocker.patch("GetUserData.get_output_key", return_value="AzureRiskyUsers.RiskyUser") mocker.patch("GetUserData.prepare_human_readable", return_value=[]) result = azure_list_risky_users(command, additional_fields=True) assert len(result[1]) == 1 assert result[1][0]["Email"] == user_email assert result[1][0]["Username"] == "Test User" assert result[1][0]["RiskLevel"] == "high" def test_prisma_cloud_get_user(self, mocker: MockerFixture): """ Given: A Command object for prisma_cloud_get_user. When: The function is called with the Command object. Then: It returns the expected tuple of readable outputs and account output. """ command = Command("PrismaCloud v2", "prisma-cloud-users-list", {"usernames": "prisma_user"}) mock_outputs = {"email": "user_email.com", "username": "prisma_user"} expected_account = [ { "Email": "user_email.com", "Source": "PrismaCloud v2", "Brand": "PrismaCloud v2", "Username": "prisma_user", "Instance": None, } ] mocker.patch( "GetUserData.run_execute_command", return_value=([mock_outputs], "Human readable output", []), ) mocker.patch("GetUserData.get_output_key", return_value="PrismaCloud.Users") mocker.patch("GetUserData.get_outputs", return_value=mock_outputs) mocker.patch("GetUserData.prepare_human_readable", return_value=[]) result = prisma_cloud_get_user(command, additional_fields=True) assert isinstance(result, tuple) assert len(result) == 2 assert isinstance(result[0], list) assert result[1] == expected_account def test_iam_get_user(self, mocker: MockerFixture): """ Given: A Command object for iam_cloud_get_user. When: The function is called with the Command object. Then: It returns the expected tuple of readable outputs and user output. """ command = Command("Okta IAM", "iam-get-user", {"user-profile": "user"}) mock_outputs = {"email": "user_email.com", "username": "name_user", "id": "user_id", "success": True} expected_account = [ { "Email": "user_email.com", "Source": "Okta IAM", "Brand": "Okta IAM", "Username": "name_user", "ID": "user_id", "Instance": None, "AdditionalFields": {"success": True}, } ] mocker.patch( "GetUserData.run_execute_command", return_value=([mock_outputs], "Human readable output", []), ) mocker.patch("GetUserData.get_output_key", return_value="IAM.Vendor") mocker.patch("GetUserData.get_outputs", return_value=mock_outputs) mocker.patch("GetUserData.prepare_human_readable", return_value=[]) result = iam_get_user(command, additional_fields=True) assert isinstance(result, tuple) assert len(result) == 2 assert isinstance(result[0], list) assert result[1] == expected_account def test_gsuite_get_user(self, mocker: MockerFixture): """ Given: A Command object for iam_cloud_get_user. When: The function is called with the Command object. Then: It returns the expected tuple of readable outputs and user output. """ command = Command("GSuiteAdmin", "gsuite-user-get", {"user": "user"}) mock_outputs = {"primaryEmail": "user_email.com", "fullName": "name_user", "id": "user_id"} expected_account = [ { "Email": "user_email.com", "Source": "GSuiteAdmin", "Brand": "GSuiteAdmin", "Username": "name_user", "ID": "user_id", "Instance": None, } ] mocker.patch( "GetUserData.run_execute_command", return_value=([mock_outputs], "Human readable output", []), ) mocker.patch("GetUserData.get_output_key", return_value="GSuite.User") mocker.patch("GetUserData.get_outputs", return_value=mock_outputs) mocker.patch("GetUserData.prepare_human_readable", return_value=[]) result = gsuite_get_user(command, additional_fields=True) assert isinstance(result, tuple) assert len(result) == 2 assert isinstance(result[0], list) assert result[1] == expected_account def test_main_successful_execution(mocker: MockerFixture): """ Given: Valid arguments for user_id, user_name, and user_email. When: The main function is called. Then: The function should execute successfully and return results for the user. """ # Mock demisto.args() mocker.patch.object( demisto, "args", return_value={ "user_id": ["123", "456"], "user_name": ["johndoe", "usertwo", 123456], "user_email": ["john@example.com"], }, ) # Mock demisto.getModules() mocker.patch.object(demisto, "getModules", return_value={}) mocker.patch.object(Modules, "is_brand_in_brands_to_run", return_value=True) mocker.patch.object(Modules, "is_brand_available", return_value=True) # Mock other necessary functions mocker.patch("GetUserData.ad_get_user", return_value=(["test"], [{"Source": "test", "Brand": "test", "Instance": None}])) mocker.patch("GetUserData.okta_get_user", return_value=([], [])) mocker.patch("GetUserData.aws_iam_get_user", return_value=([], [])) mocker.patch("GetUserData.msgraph_user_get", return_value=([], [])) mocker.patch("GetUserData.get_core_and_xdr_data", return_value=([], [])) mocker.patch("GetUserData.azure_get_risky_user", return_value=([], [])) mocker.patch("GetUserData.prisma_cloud_get_user", return_value=([], [])) mocker.patch("GetUserData.iam_get_user", return_value=([], [])) mocker.patch("GetUserData.gsuite_get_user", return_value=([], [])) # Mock return_results mock_return_results = mocker.patch("GetUserData.return_results") # Call the main function main() # Assert that return_results was called assert mock_return_results.called def test_main_no_user_info_provided(mocker: MockerFixture): """ Given: No user identification information is provided in the arguments. When: The main function is called. Then: The function should raise a ValueError. """ # Mock demisto.args() to return empty arguments mocker.patch.object(demisto, "args", return_value={}) # Mock demisto.getModules() mocker.patch.object(demisto, "getModules", return_value={}) # Mock return_error mock_return_error = mocker.patch("GetUserData.return_error") # Call the main function main() # Assert that return_error was called with the correct error message mock_return_error.assert_called_once_with( "Failed to execute get-user-data. Error: At least one of the following arguments must" " be specified: user_id, user_name, user_email or user_sid." ) def test_main_domain_without_username(mocker: MockerFixture): """ Given: A domain is provided in the arguments without a user_name. When: The main function is called. Then: The function should raise a ValueError. """ # Mock demisto.args() to return a domain without a user_name mocker.patch.object( demisto, "args", return_value={ "domain": "example.com", "user_id": "123", }, ) # Mock demisto.getModules() mocker.patch.object(demisto, "getModules", return_value={}) # Mock return_error mock_return_error = mocker.patch("GetUserData.return_error") # Call the main function main() # Assert that return_error was called with the correct error message mock_return_error.assert_called_once_with( "Failed to execute get-user-data. Error: When specifying the domain argument, " "the user_name argument must also be provided." ) def test_get_data_with_found_user(mocker: MockerFixture): modules = Modules({}, ["Brand1"]) mocker.patch.object(Modules, "is_brand_in_brands_to_run", return_value=True) mocker.patch.object(Modules, "is_brand_available", return_value=True) # Mock other necessary functions mock_get_user = mocker.patch( "GetUserData.ad_get_user", return_value=(["test"], [{"Source": "test", "Brand": "test", "Username": "test user", "Instance": None}]), ) result = get_data( modules=modules, brand_name="Brand1", command_name="test_command", arg_name="test_arg", arg_value="test_value", cmd=mock_get_user, additional_fields=True, ) assert result[1] assert result[1][0].get("Status") == "found" def test_get_data_without_found_user(mocker: MockerFixture): modules = Modules({}, ["Brand1"]) mocker.patch.object(Modules, "is_brand_in_brands_to_run", return_value=True) mocker.patch.object(Modules, "is_brand_available", return_value=True) # Mock other necessary functions mock_get_user = mocker.patch( "GetUserData.ad_get_user", return_value=([], [{"Source": "test", "Brand": "test", "Instance": "inst"}]) ) result = get_data( modules=modules, brand_name="Brand1", command_name="test_command", arg_name="test_arg", arg_value="test_value", cmd=mock_get_user, additional_fields=True, ) assert result[1][0].get("Status") == "User not found - userId: test_value." def test_xdr_and_core_list_all_users_with_list_non_risky_users_true_and_additional_fields_true(mocker: MockerFixture): """ Given: list_non_risky_users is set to True and additional_fields=True. A user with an email that did not appear in the email_list was found. When: xdr_and_core_list_all_users is called. Then: It should process both risky and non-risky users with additional fields. """ # Arrange risky_commands = [Command("Cortex XDR - IR", "xdr-list-risky-users", {"user_id": "risky1"})] list_users_command = Command("Cortex XDR - IR", "xdr-list-users", {}) outputs_key_field = "PaloAltoNetworksXDR" additional_fields = True list_non_risky_users = True email_list = ["user1@example.com", "user2@example.com"] # Mock risky users results risky_readable_outputs = [mocker.Mock(spec=CommandResults)] risky_users = [{"ID": "risky1", "Email": "risky@example.com", "Status": "found", "additional_field": "risky_value"}] # Mock final results after processing non-risky users # Include a user found that wasn't in the email_list final_readable_outputs = [mocker.Mock(spec=CommandResults), mocker.Mock(spec=CommandResults)] final_users = [ {"ID": "risky1", "Email": "risky@example.com", "Status": "found", "additional_field": "risky_value"}, {"ID": "user1", "Email": "user1@example.com", "Status": "found", "additional_field": "user1_value"}, {"ID": "user2", "Email": "user2@example.com", "Status": "found", "additional_field": "user2_value"}, { "ID": "unexpected", "Email": "unexpected@example.com", "Status": "found", "additional_field": "unexpected_value", }, # Not in email_list ] mock_run_list_risky_users = mocker.patch( "GetUserData.run_list_risky_users_command", return_value=(risky_readable_outputs, risky_users) ) mock_run_list_users = mocker.patch("GetUserData.run_list_users_command", return_value=(final_readable_outputs, final_users)) # Act readable_outputs, users = xdr_and_core_list_all_users( risky_commands, list_users_command, outputs_key_field, additional_fields, list_non_risky_users, email_list ) # Assert assert readable_outputs == final_readable_outputs assert users == final_users assert len(users) == 4 # 1 risky + 3 non-risky users # Verify that a user not in email_list was found unexpected_users = [user for user in users if user["Email"] == "unexpected@example.com"] assert len(unexpected_users) == 1 assert unexpected_users[0]["additional_field"] == "unexpected_value" mock_run_list_risky_users.assert_called_once_with(risky_commands, True, outputs_key_field) mock_run_list_users.assert_called_once_with( list_users_command, True, outputs_key_field, email_list, risky_users, risky_readable_outputs ) def test_xdr_and_core_list_all_users_with_list_non_risky_users_false(mocker: MockerFixture): """ Given: list_non_risky_users is set to False. When: xdr_and_core_list_all_users is called. Then: It should only process risky users and not call run_list_users_command. """ # Arrange risky_commands = [ Command("Cortex XDR - IR", "xdr-list-risky-users", {"user_id": "risky1"}), Command("Cortex Core - IR", "core-list-risky-users", {"user_id": "risky2"}), ] list_users_command = Command("Cortex XDR - IR", "xdr-list-users", {}) outputs_key_field = "PaloAltoNetworksXDR" additional_fields = True list_non_risky_users = False email_list = ["user1@example.com", "user2@example.com"] # Mock risky users results risky_readable_outputs = [mocker.Mock(spec=CommandResults), mocker.Mock(spec=CommandResults)] risky_users = [ {"ID": "risky1", "Email": "risky1@example.com", "Status": "found", "risk_level": "HIGH"}, {"ID": "risky2", "Email": "risky2@example.com", "Status": "found", "risk_level": "MEDIUM"}, ] mock_run_list_risky_users = mocker.patch( "GetUserData.run_list_risky_users_command", return_value=(risky_readable_outputs, risky_users) ) mock_run_list_users = mocker.patch("GetUserData.run_list_users_command") # Act readable_outputs, users = xdr_and_core_list_all_users( risky_commands, list_users_command, outputs_key_field, additional_fields, list_non_risky_users, email_list ) # Assert assert readable_outputs == risky_readable_outputs assert users == risky_users assert len(users) == 2 # Only risky users mock_run_list_risky_users.assert_called_once_with(risky_commands, additional_fields, outputs_key_field) mock_run_list_users.assert_not_called() # Should not be called when list_non_risky_users=False def test_run_list_risky_users_command_single_command_success(mocker: MockerFixture): """ Given: A single Command object for listing risky users with valid response. When: run_list_risky_users_command is called with the command. Then: It should return readable outputs and user data with 'found' status. """ # Arrange command = Command("Cortex XDR - IR", "xdr-list-risky-users", {"user_id": "test_user"}) commands = [command] additional_fields = True outputs_key_field = "PaloAltoNetworksXDR" mock_entry_context = [ { "PaloAltoNetworksXDR.RiskyUser": { "id": "test_user", "risk_level": "HIGH", "email": "test@example.com", "department": "IT", }, "instance": "xdr_instance", } ] mock_run_execute_command = mocker.patch( "GetUserData.run_execute_command", return_value=(mock_entry_context, "Human readable output", []) ) mock_prepare_human_readable = mocker.patch( "GetUserData.prepare_human_readable", return_value=[mocker.Mock(spec=CommandResults)] ) # Act readable_outputs, users = run_list_risky_users_command(commands, additional_fields, outputs_key_field) # Assert assert len(readable_outputs) == 1 assert len(users) == 1 user = users[0] assert user["ID"] == "test_user" assert user["Username"] == "test_user" assert user["RiskLevel"] == "HIGH" assert user["Email"] == "test@example.com" assert user["Source"] == "Cortex XDR - IR" assert user["Brand"] == "Cortex XDR - IR" assert user["Instance"] == "xdr_instance" assert user["Status"] == "found" assert "AdditionalFields" in user assert user["AdditionalFields"]["department"] == "IT" mock_run_execute_command.assert_called_once_with("xdr-list-risky-users", {"user_id": "test_user"}) mock_prepare_human_readable.assert_called_once() def test_run_list_risky_users_command_multiple_commands_success(mocker: MockerFixture): """ Given: Multiple Command objects for listing risky users. When: run_list_risky_users_command is called with the commands. Then: It should return readable outputs and user data for all commands. """ # Arrange command1 = Command("Cortex Core - IR", "xdr-list-risky-users", {"user_id": "user1"}) command2 = Command("Cortex Core - IR", "core-list-risky-users", {"user_id": "user2"}) commands = [command1, command2] additional_fields = False outputs_key_field = "Core" mock_entry_contexts = [ [{"Core.RiskyUser": {"id": "user1", "risk_level": "HIGH", "email": "user1@example.com"}, "instance": "xdr_instance"}], [{"Core.RiskyUser": {"id": "user2", "risk_level": "MEDIUM", "email": "user2@example.com"}, "instance": "core_instance"}], ] mock_run_execute_command = mocker.patch( "GetUserData.run_execute_command", side_effect=[(mock_entry_contexts[0], "Output 1", []), (mock_entry_contexts[1], "Output 2", [])], ) # Act readable_outputs, users = run_list_risky_users_command(commands, additional_fields, outputs_key_field) # Assert assert len(readable_outputs) == 2 assert len(users) == 2 # Check first user user1 = users[0] assert user1["ID"] == "user1" assert user1["Username"] == "user1" assert user1["RiskLevel"] == "HIGH" assert user1["Email"] == "user1@example.com" assert user1["Source"] == "Cortex Core - IR" assert user1["Instance"] == "xdr_instance" assert user1["Status"] == "found" assert "AdditionalFields" not in user1 # additional_fields=False # Check second user user2 = users[1] assert user2["ID"] == "user2" assert user2["Username"] == "user2" assert user2["RiskLevel"] == "MEDIUM" assert user2["Email"] == "user2@example.com" assert user2["Source"] == "Cortex Core - IR" assert user2["Instance"] == "core_instance" assert user2["Status"] == "found" assert mock_run_execute_command.call_count == 2 def test_run_list_risky_users_command_user_not_found(mocker: MockerFixture): """ Given: A Command object that returns empty user data (user not found). When: run_list_risky_users_command is called with the command. Then: It should return user data with 'User not found' status. """ # Arrange command = Command("Cortex XDR - IR", "xdr-list-risky-users", {"user_id": "nonexistent_user"}) commands = [command] additional_fields = True outputs_key_field = "PaloAltoNetworksXDR" mock_entry_context = [{"PaloAltoNetworksXDR.RiskyUser": {}, "instance": "xdr_instance"}] mocker.patch("GetUserData.run_execute_command", return_value=(mock_entry_context, "No user found", [])) # Act _, users = run_list_risky_users_command(commands, additional_fields, outputs_key_field) # Assert assert len(users) == 1 user = users[0] # User should only have Source, Brand, and Instance keys when not found expected_keys = {"Source", "Brand", "Instance"} actual_keys = set(user.keys()) - {"Status"} # Exclude Status key for comparison assert actual_keys == expected_keys assert user["Source"] == "Cortex XDR - IR" assert user["Brand"] == "Cortex XDR - IR" assert user["Instance"] == "xdr_instance" assert user["Status"] == "User not found - userId: nonexistent_user." def test_run_list_risky_users_command_with_errors(mocker: MockerFixture): """ Given: A Command object that returns errors during execution. When: run_list_risky_users_command is called with the command. Then: It should return readable outputs including error messages. """ # Arrange command = Command("Cortex XDR - IR", "xdr-list-risky-users", {"user_id": "test_user"}) commands = [command] additional_fields = True outputs_key_field = "PaloAltoNetworksXDR" mock_entry_context = [{"PaloAltoNetworksXDR.RiskyUser": {"id": "test_user", "risk_level": "LOW"}, "instance": "xdr_instance"}] error_result = mocker.Mock(spec=CommandResults) mocker.patch("GetUserData.run_execute_command", return_value=(mock_entry_context, "Human readable output", [error_result])) # Act readable_outputs, users = run_list_risky_users_command(commands, additional_fields, outputs_key_field) # Assert assert len(readable_outputs) == 2 # 1 error + 1 human readable assert error_result in readable_outputs assert len(users) == 1 user = users[0] assert user["Status"] == "found" assert user["RiskLevel"] == "LOW" def test_run_list_risky_users_command_empty_commands_list(): """ Given: An empty list of commands. When: run_list_risky_users_command is called with the empty list. Then: It should return empty readable outputs and users lists. """ # Arrange commands = [] additional_fields = True outputs_key_field = "PaloAltoNetworksXDR" # Act readable_outputs, users = run_list_risky_users_command(commands, additional_fields, outputs_key_field) # Assert assert readable_outputs == [] assert users == [] def test_run_list_risky_users_command_additional_fields_true(mocker: MockerFixture): """ Given: A Command object with additional_fields set to True. When: run_list_risky_users_command is called. Then: It should include AdditionalFields in the user data. """ # Arrange command = Command("Cortex XDR - IR", "xdr-list-risky-users", {"user_id": "test_user"}) commands = [command] additional_fields = True outputs_key_field = "PaloAltoNetworksXDR" mock_entry_context = [ { "PaloAltoNetworksXDR.RiskyUser": { "id": "test_user", "risk_level": "HIGH", "email": "test@example.com", "department": "IT", "extra_field": "extra_value", }, "instance": "xdr_instance", } ] mocker.patch("GetUserData.run_execute_command", return_value=(mock_entry_context, "Human readable output", [])) # Act _, users = run_list_risky_users_command(commands, additional_fields, outputs_key_field) # Assert assert len(users) == 1 user = users[0] # Should have AdditionalFields when additional_fields=True assert "AdditionalFields" in user assert user["AdditionalFields"]["department"] == "IT" assert user["AdditionalFields"]["extra_field"] == "extra_value" assert user["ID"] == "test_user" assert user["Username"] == "test_user" assert user["RiskLevel"] == "HIGH" assert user["Email"] == "test@example.com" assert user["Status"] == "found" def test_run_list_users_command_no_email_one_user_additional_fields_false(mocker: MockerFixture): """ Given: No email given, one user appears under users but additional_fields = False When: run_list_users_command is called. Then: It should return the existing users without modification and not call the command. """ # Arrange command = Command("Cortex XDR - IR", "xdr-list-users", {}) additional_fields = False outputs_key_field = "PaloAltoNetworksXDR" email_list = [] existing_users = [{"Email": "existing@example.com", "ID": "existing_id", "Status": "found", "AdditionalFields": {}}] readable_outputs_list = [] # Mock the command execution and debug logging mock_run_execute_command = mocker.patch("GetUserData.run_execute_command") mock_debug = mocker.patch("GetUserData.demisto.debug") # Act readable_outputs, users = run_list_users_command( command, additional_fields, outputs_key_field, email_list, existing_users, readable_outputs_list ) # Assert # Command should not be called since email_list is empty mock_run_execute_command.assert_not_called() # Debug message should be logged about no emails to search for mock_debug.assert_called_with("Did not recieve any email to search for, skipping list users command.") # Users should remain unchanged assert readable_outputs == [] assert users == existing_users # Should remain unchanged since email_list is empty assert len(users) == 1 assert users[0]["Email"] == "existing@example.com" def test_run_list_users_command_no_email_one_user_additional_fields_true(mocker: MockerFixture): """ Given: No email given, one user appears under users but additional_fields = True When: run_list_users_command is called. Then: It should update the existing user with additional fields from the command output. """ # Arrange command = Command("Cortex XDR - IR", "xdr-list-users", {}) additional_fields = True outputs_key_field = "PaloAltoNetworksXDR" email_list = [] existing_users = [ {"Email": "existing@example.com", "ID": "existing_id", "Status": "found", "AdditionalFields": {"original": "value"}} ] readable_outputs_list = [] mock_entry_context = [ { "PaloAltoNetworksXDR.User": [ {"id": "existing_id", "user_email": "existing@example.com", "department": "Engineering", "location": "NY"} ], "instance": "xdr_instance", } ] mocker.patch("GetUserData.run_execute_command", return_value=(mock_entry_context, "Human readable output", [])) # Act readable_outputs, users = run_list_users_command( command, additional_fields, outputs_key_field, email_list, existing_users, readable_outputs_list ) # Assert assert readable_outputs == [] assert len(users) == 1 user = users[0] assert user["Email"] == "existing@example.com" assert user["AdditionalFields"]["original"] == "value" assert user["AdditionalFields"]["department"] == "Engineering" assert user["AdditionalFields"]["location"] == "NY" def test_run_list_users_command_one_email_no_user_found_additional_fields_false(mocker: MockerFixture): """ Given: One email given, no user appears under users but the mail was not found after listing the users. When: run_list_users_command is called with additional_fields=False. Then: It should add the user with 'not found' status. """ # Arrange command = Command("Cortex XDR - IR", "xdr-list-users", {}) additional_fields = False outputs_key_field = "PaloAltoNetworksXDR" email_list = ["notfound@example.com"] existing_users = [] readable_outputs_list = [] mock_entry_context = [{"PaloAltoNetworksXDR.User": [], "instance": "xdr_instance"}] mocker.patch("GetUserData.run_execute_command", return_value=(mock_entry_context, "Human readable output", [])) # Act _, users = run_list_users_command( command, additional_fields, outputs_key_field, email_list, existing_users, readable_outputs_list ) # Assert assert len(users) == 1 user = users[0] assert user["Email"] == "notfound@example.com" assert user["Source"] == "Cortex XDR - IR" assert user["Status"] == "not found" def test_run_list_users_command_email_in_both_lists_additional_fields_false(mocker: MockerFixture): """ Given: The email_list contains an email which is also related to a user that appears under users and additional_fields=false When: run_list_users_command is called. Then: It should find the user and not update additional fields since additional_fields=False. """ # Arrange command = Command("Cortex XDR - IR", "xdr-list-users", {}) additional_fields = False outputs_key_field = "PaloAltoNetworksXDR" email_list = ["shared@example.com"] existing_users = [ { "Email": "shared@example.com", "ID": "existing_id", "Source": "Previous Source", "Status": "found", } ] readable_outputs_list = [] mock_entry_context = [ { "PaloAltoNetworksXDR.User": [ {"id": "existing_id", "user_email": "shared@example.com", "department": "Engineering", "location": "SF"} ], "instance": "xdr_instance", } ] mocker.patch("GetUserData.run_execute_command", return_value=(mock_entry_context, "Human readable output", [])) # Act _, users = run_list_users_command( command, additional_fields, outputs_key_field, email_list, existing_users, readable_outputs_list ) # Assert assert len(users) == 1 user = users[0] assert user["Email"] == "shared@example.com" assert user["ID"] == "existing_id" assert "AdditionalFields" not in user def test_run_list_users_command_multiple_emails_mixed_results(mocker: MockerFixture): """ Given: Multiple emails with mixed results - some found, some not found, with additional_fields=True When: run_list_users_command is called. Then: It should process found users and add not found users with appropriate status. """ # Arrange command = Command("Cortex XDR - IR", "xdr-list-users", {}) additional_fields = True outputs_key_field = "PaloAltoNetworksXDR" email_list = ["found1@example.com", "notfound@example.com", "found2@example.com"] existing_users = [] readable_outputs_list = [] mock_entry_context = [ { "PaloAltoNetworksXDR.User": [ {"id": "user1_id", "user_email": "found1@example.com", "department": "IT", "risk_level": "LOW"}, {"id": "user2_id", "user_email": "found2@example.com", "department": "HR", "risk_level": "MEDIUM"}, ], "instance": "xdr_instance", } ] mocker.patch("GetUserData.run_execute_command", return_value=(mock_entry_context, "Human readable output", [])) # Act _, users = run_list_users_command( command, additional_fields, outputs_key_field, email_list, existing_users, readable_outputs_list ) # Assert assert len(users) == 3 # 2 found + 1 not found found_users = [user for user in users if user["Status"] == "found"] not_found_users = [user for user in users if user["Status"] == "not found"] assert len(found_users) == 2 assert len(not_found_users) == 1 # Check found users have additional fields found1 = next(user for user in found_users if user["Email"] == "found1@example.com") assert found1["AdditionalFields"]["department"] == "IT" assert found1["RiskLevel"] == "LOW" found2 = next(user for user in found_users if user["Email"] == "found2@example.com") assert found2["AdditionalFields"]["department"] == "HR" assert found2["RiskLevel"] == "MEDIUM" # Check not found user not_found = not_found_users[0] assert not_found["Email"] == "notfound@example.com" assert not_found["Status"] == "not found" def test_run_list_users_command_empty_user_email_field(mocker: MockerFixture): """ Given: API returns users without user_email field When: run_list_users_command is called. Then: It should skip users without email and mark searched emails as not found. """ # Arrange command = Command("Cortex XDR - IR", "xdr-list-users", {}) additional_fields = False outputs_key_field = "PaloAltoNetworksXDR" email_list = ["search@example.com"] existing_users = [] readable_outputs_list = [] mock_entry_context = [ { "PaloAltoNetworksXDR.User": [ {"id": "user_without_email", "department": "IT"}, # No user_email field {"id": "user_with_empty_email", "user_email": "", "department": "HR"}, # Empty user_email ], "instance": "xdr_instance", } ] mocker.patch("GetUserData.run_execute_command", return_value=(mock_entry_context, "Human readable output", [])) # Act _, users = run_list_users_command( command, additional_fields, outputs_key_field, email_list, existing_users, readable_outputs_list ) # Assert assert len(users) == 1 # Only the not found user user = users[0] assert user["Email"] == "search@example.com" assert user["Status"] == "not found" def test_run_list_users_command_api_command_failure(mocker: MockerFixture): """ Given: run_execute_command returns an error entry and no users When: run_list_users_command is called. Then: It should receive the error entry and mark searched emails as not found. """ # Arrange command = Command("Cortex XDR - IR", "xdr-list-users", {}) additional_fields = False outputs_key_field = "PaloAltoNetworksXDR" email_list = ["test@example.com"] existing_users = [] readable_outputs_list = [] # Mock run_execute_command to return error entry and empty user list error_result = mocker.Mock(spec=CommandResults) mock_entry_context = [{"PaloAltoNetworksXDR.User": [], "instance": "xdr_instance"}] mocker.patch("GetUserData.run_execute_command", return_value=(mock_entry_context, "Human readable output", [error_result])) # Act readable_outputs, users = run_list_users_command( command, additional_fields, outputs_key_field, email_list, existing_users, readable_outputs_list ) # Assert assert error_result in readable_outputs assert len(users) == 1 # User not found should be added assert users[0]["Email"] == "test@example.com" assert users[0]["Status"] == "not found" def test_run_list_users_command_risky_user_not_in_email_list_additional_fields_false(mocker: MockerFixture): """ Given: Email in risky_users but not in email_list with additional_fields=False When: run_list_users_command is called. Then: It should not process the risky user since additional_fields=False. """ # Arrange command = Command("Cortex XDR - IR", "xdr-list-users", {}) additional_fields = False outputs_key_field = "PaloAltoNetworksXDR" email_list = ["search@example.com"] existing_users = [ { "Email": "risky@example.com", "ID": "risky_id", "Source": "Risky Source", } ] readable_outputs_list = [] mock_entry_context = [ { "PaloAltoNetworksXDR.User": [ {"id": "risky_id", "user_email": "risky@example.com", "department": "Security"}, ], "instance": "xdr_instance", } ] mocker.patch("GetUserData.run_execute_command", return_value=(mock_entry_context, "Human readable output", [])) # Act _, users = run_list_users_command( command, additional_fields, outputs_key_field, email_list, existing_users, readable_outputs_list ) # Assert assert len(users) == 2 # Original risky user + not found search user risky_user = next(user for user in users if user["Email"] == "risky@example.com") assert "AdditionalFields" not in risky_user not_found_user = next(user for user in users if user["Email"] == "search@example.com") assert not_found_user["Status"] == "not found" def test_run_list_users_command_empty_outputs_from_api(mocker: MockerFixture): """ Given: API returns valid structure but empty user list When: run_list_users_command is called. Then: It should mark all searched emails as not found. """ # Arrange command = Command("Cortex XDR - IR", "xdr-list-users", {}) additional_fields = True outputs_key_field = "PaloAltoNetworksXDR" email_list = ["test1@example.com", "test2@example.com"] existing_users = [] readable_outputs_list = [] mock_entry_context = [ { "PaloAltoNetworksXDR.User": [], # Empty user list "instance": "xdr_instance", } ] mocker.patch("GetUserData.run_execute_command", return_value=(mock_entry_context, "Human readable output", [])) # Act _, users = run_list_users_command( command, additional_fields, outputs_key_field, email_list, existing_users, readable_outputs_list ) # Assert assert len(users) == 2 # Both emails marked as not found assert all(user["Status"] == "not found" for user in users) assert {user["Email"] for user in users} == set(email_list) # --- helpers to mute all other adapters so main can run quietly --- def _mute_all_other_adapters(mocker: MockerFixture, except_fn: str | None = None): fns = { "ad_get_user", "okta_get_user", "aws_iam_get_user", "msgraph_user_get", "prisma_cloud_get_user", "iam_get_user", # <- shared by Okta IAM and AWS-ILM "gsuite_get_user", "azure_get_risky_user", "azure_list_risky_users", } for fn in fns: if fn == except_fn: continue mocker.patch(f"GetUserData.{fn}", return_value=([], [])) # -------------- Testing Calling the right argument per command -------------- # ---------- Username flows (no domain) ---------- @pytest.mark.parametrize( "brand_name,command_name,adapter_fn,expected_key,expected_value", [ ("Active Directory Query v2", "ad-get-user", "ad_get_user", "username", "alice"), ("Okta v2", "okta-get-user", "okta_get_user", "username", "alice"), ("AWS - IAM", "aws-iam-get-user", "aws_iam_get_user", "userName", "alice"), ("Microsoft Graph User", "msgraph-user-get", "msgraph_user_get", "user", "alice"), ("PrismaCloud v2", "prisma-cloud-users-list", "prisma_cloud_get_user", "usernames", "alice"), ("Okta IAM", "iam-get-user", "iam_get_user", "user-profile", '{"login":"alice"}'), ("AWS-ILM", "iam-get-user", "iam_get_user", "user-profile", '{"login":"alice"}'), ], ) def test_username_arg_mapping_to_adapter( mocker: MockerFixture, brand_name, command_name, adapter_fn, expected_key, expected_value ): """ Given: - calling get-user-data with username = alice. - brand_name = brand_name. When: - main() executes by username flows. Then: - The right command is being called with the right argument name. """ mocker.patch.object(demisto, "args", return_value={"user_name": ["alice"]}) mocker.patch.object(demisto, "getModules", return_value={}) mocker.patch.object(Modules, "is_brand_in_brands_to_run", return_value=True) mocker.patch.object(Modules, "is_brand_available", return_value=True) mocker.patch("GetUserData.get_core_and_xdr_data", return_value=([], [])) mocker.patch("GetUserData.return_results") _mute_all_other_adapters(mocker, except_fn=adapter_fn) seen = {"ok": False} def _assert_adapter(command: Command, additional_fields: bool): # Only assert for the exact brand+command under test; ignore other calls to the same adapter. if command.brand != brand_name or command.name != command_name: return ([], []) assert command.args.get(expected_key) == expected_value assert command.args.get("using-brand") == brand_name if expected_key == "username": assert "name" not in command.args # regression guard seen["ok"] = True return ([], []) mocker.patch(f"GetUserData.{adapter_fn}", side_effect=_assert_adapter) main() assert seen["ok"] is True # ---------- Username flow with domain prefix (DOMAIN\\username) ---------- def test_domain_username_branch_uses_username_key_for_ad(mocker: MockerFixture): """ Given: - calling get-user-data with username = ACME\\alice. - brand_name = brand_name. When: - main() executes by username flows. Then: - The right command is being called with the right argument name. """ mocker.patch.object(demisto, "args", return_value={"user_name": ["ACME\\alice"]}) mocker.patch.object(demisto, "getModules", return_value={}) mocker.patch.object(Modules, "is_brand_in_brands_to_run", return_value=True) mocker.patch.object(Modules, "is_brand_available", return_value=True) mocker.patch("GetUserData.get_core_and_xdr_data", return_value=([], [])) mocker.patch("GetUserData.return_results") _mute_all_other_adapters(mocker, except_fn="ad_get_user") hit = {"seen": False} def _assert_ad(command: Command, additional_fields: bool): if command.brand != "Active Directory Query v2" or command.name != "ad-get-user": return ([], []) assert command.args.get("username") == "alice" assert "name" not in command.args assert command.args.get("using-brand") == "Active Directory Query v2" hit["seen"] = True return ([], []) mocker.patch("GetUserData.ad_get_user", side_effect=_assert_ad) main() assert hit["seen"] is True # ---------- User ID flows ---------- @pytest.mark.parametrize( "brand_name,command_name,adapter_fn,expected_key,expected_value", [ ("Okta v2", "okta-get-user", "okta_get_user", "userId", "u123"), ("Microsoft Graph User", "msgraph-user-get", "msgraph_user_get", "user", "u123"), ("AzureRiskyUsers", "azure-risky-user-get", "azure_get_risky_user", "id", "u123"), ("Okta IAM", "iam-get-user", "iam_get_user", "user-profile", '{"id":"u123"}'), ("AWS-ILM", "iam-get-user", "iam_get_user", "user-profile", '{"id":"u123"}'), ("GSuiteAdmin", "gsuite-user-get", "gsuite_get_user", "user", "u123"), ], ) def test_userid_arg_mapping_to_adapter(mocker: MockerFixture, brand_name, command_name, adapter_fn, expected_key, expected_value): """ Given: - calling get-user-data with user_id = u123. - brand_name = brand_name. When: -main() executes by user ID flows. Then: - The right command is being called with the right argument name. """ mocker.patch.object(demisto, "args", return_value={"user_id": ["u123"]}) mocker.patch.object(demisto, "getModules", return_value={}) mocker.patch.object(Modules, "is_brand_in_brands_to_run", return_value=True) mocker.patch.object(Modules, "is_brand_available", return_value=True) mocker.patch("GetUserData.get_core_and_xdr_data", return_value=([], [])) mocker.patch("GetUserData.return_results") _mute_all_other_adapters(mocker, except_fn=adapter_fn) seen = {"ok": False} def _assert_adapter(command: Command, additional_fields: bool): if command.brand != brand_name or command.name != command_name: return ([], []) assert command.args.get(expected_key) == expected_value assert command.args.get("using-brand") == brand_name seen["ok"] = True return ([], []) mocker.patch(f"GetUserData.{adapter_fn}", side_effect=_assert_adapter) main() assert seen["ok"] is True # ---------- Email flows ---------- @pytest.mark.parametrize( "brand_name,command_name,adapter_fn,expected_key,expected_value", [ ("Active Directory Query v2", "ad-get-user", "ad_get_user", "email", "john@example.com"), ("AzureRiskyUsers", "azure-risky-users-list", "azure_list_risky_users", "filter_email", "john@example.com"), ("Okta IAM", "iam-get-user", "iam_get_user", "user-profile", '{"email":"john@example.com"}'), ("AWS-ILM", "iam-get-user", "iam_get_user", "user-profile", '{"email":"john@example.com"}'), ("GSuiteAdmin", "gsuite-user-get", "gsuite_get_user", "user", "john@example.com"), ], ) def test_email_arg_mapping_to_adapter(mocker: MockerFixture, brand_name, command_name, adapter_fn, expected_key, expected_value): """ Given: - calling get-user-data with user_email = john@example.com. - brand_name = brand_name. When: - main() executes by email flows. Then: - The right command is being called with the right argument name. """ mocker.patch.object(demisto, "args", return_value={"user_email": ["john@example.com"]}) mocker.patch.object(demisto, "getModules", return_value={}) mocker.patch.object(Modules, "is_brand_in_brands_to_run", return_value=True) mocker.patch.object(Modules, "is_brand_available", return_value=True) mocker.patch("GetUserData.get_core_and_xdr_data", return_value=([], [])) mocker.patch("GetUserData.return_results") _mute_all_other_adapters(mocker, except_fn=adapter_fn) seen = {"ok": False} def _assert_adapter(command: Command, additional_fields: bool): if command.brand != brand_name or command.name != command_name: return ([], []) assert command.args.get(expected_key) == expected_value assert command.args.get("using-brand") == brand_name seen["ok"] = True return ([], []) mocker.patch(f"GetUserData.{adapter_fn}", side_effect=_assert_adapter) main() assert seen["ok"] is True