def get_effective_user(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs): phantom.debug("get_effective_user() called") ################################################################################ # Find the user and user type that launched this playbook. ################################################################################ get_effective_user__user_id = None get_effective_user__user_type = None ################################################################################ ## Custom Code Start ################################################################################ effective_user_id = phantom.get_effective_user() url = phantom.build_phantom_rest_url('ph_user', effective_user_id) response_json = phantom.requests.get(url, verify=False).json() get_effective_user__user_type = response_json['type'] get_effective_user__user_id = effective_user_id ################################################################################ ## Custom Code End ################################################################################ phantom.save_run_data(key="get_effective_user:user_id", value=json.dumps(get_effective_user__user_id)) phantom.save_run_data(key="get_effective_user:user_type", value=json.dumps(get_effective_user__user_type)) user_decision(container=container) return
def select_indicators(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs): phantom.debug("select_indicators() called") # set user and message variables for phantom.prompt call custom_format__output = json.loads( phantom.get_run_data(key="custom_format:output")) user_id = phantom.get_effective_user() url = phantom.build_phantom_rest_url('ph_user', user_id) response = phantom.requests.get(url, verify=False).json() user = response['username'] message = """Please review the list of suspect indicators and select an action.\n\n{0}""".format( custom_format__output) indicator_records = phantom.collect2( container=container, datapath=[ "get_suspect_indicators:custom_function_result.data.*.indicator_value" ], action_results=results) indicator_value_list = [item[0] for item in indicator_records] # dynamic response generation response_types = [] parameters = None for ind_val in indicator_value_list: response_types.append({ "prompt": "{0}".format(ind_val), "options": { "type": "list", "choices": ["Block", "Tag as Safe", "Do Nothing"] } }) phantom.prompt2(container=container, user=user, message=message, respond_in_mins=30, name="select_indicators", parameters=parameters, response_types=response_types, callback=process_responses) return
def event_details(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs): phantom.debug("event_details() called") ################################################################################ # A dynamic prompt to list out details for each container so that the user can # decide which to merge. ################################################################################ # set user and message variables for phantom.prompt call effective_user_id = phantom.get_effective_user() url = phantom.build_phantom_rest_url('ph_user', effective_user_id) response = phantom.requests.get(url, verify=False).json() user = response.get('username') message = """{0}""" # parameter list for template variable replacement parameters = [ "custom_format:custom_function:output", ] # fetch data for dynamic response container_data = phantom.collect2(container=container, datapath=['find_related_events:custom_function_result.data.*.container_id', 'find_related_events:custom_function_result.data.*.container_name'], action_results=results) container_id_list = [item[0] for item in container_data] container_name_list = [item[1] for item in container_data] #Dynamic Responses: response_types = [] for container_id, container_name in zip(container_id_list, container_name_list): response_types.append({ "prompt": "Event {0}: {1}".format(container_id, container_name), "options": { "type": "list", "choices": [ "Merge Into Case", "Ignore", ] }, }) phantom.save_run_data(value=json.dumps(container_id_list), key="container_list", auto=True) phantom.prompt2(container=container, user=user, message=message, respond_in_mins=30, name="event_details", parameters=parameters, response_types=response_types, callback=process_responses) return
def container_update(container_input=None, name=None, description=None, label=None, owner=None, sensitivity=None, severity=None, status=None, tags=None, input_json=None, **kwargs): """ Allows updating various attributes of a container in a single custom function. Any attributes of a container not listed can be updated via the input_json parameter. Args: container_input (CEF type: phantom container id): Supports a container id or container dictionary name: Optional parameter to change container name description: Optional parameter to change the container description label (CEF type: phantom container label): Optional parameter to change the container label owner: Optional parameter to change the container owner. Accepts a username or role name or keyword "current" to set the currently running playbook user as the owner. sensitivity: Optional parameter to change the container sensitivity. severity: Optional parameter to change the container severity. status: Optional parameter to change the container status. tags: Optional parameter to change the container tags. Must be in the format of a comma separated list. input_json: Optional parameter to modify any extra attributes of a container. Input_json will be merged with other inputs. In the event of a conflict, input_json will take precedence. Returns a JSON-serializable object that implements the configured data paths: """ ############################ Custom Code Goes Below This Line ################################# import json import phantom.rules as phantom outputs = {} update_dict = {} if isinstance(container_input, int): container = phantom.get_container(container_input) elif isinstance(container_input, dict): container = container_input else: raise TypeError("container_input is neither a int or a dictionary") if name: update_dict['name'] = name if description: update_dict['description'] = description if label: update_dict['label'] = label if owner: # If keyword 'current' entered then translate effective_user id to a username if owner.lower() == 'current': update_dict['owner_id'] = phantom.get_effective_user() else: # Attempt to translate name to owner_id url = phantom.build_phantom_rest_url( 'ph_user') + f'?_filter_username="******"' data = phantom.requests.get(url, verify=False).json().get('data') if data and len(data) == 1: update_dict['owner_id'] = data[0]['id'] elif data and len(data) > 1: phantom.error(f'Multiple matches for owner "{owner}"') else: # Attempt to translate name to role_id url = phantom.build_phantom_rest_url( 'role') + f'?_filter_name="{owner}"' data = phantom.requests.get(url, verify=False).json().get('data') if data and len(data) == 1: update_dict['role_id'] = data[0]['id'] elif data and len(data) > 1: phantom.error(f'Multiple matches for role "{owner}"') else: phantom.error(f'"{owner}" is not a valid username or role') if sensitivity: update_dict['sensitivity'] = sensitivity if severity: update_dict['severity'] = severity if status: update_dict['status'] = status if tags: tags = tags.replace(" ", "").split(",") update_dict['tags'] = tags if input_json: json_dict = json.loads(input_json) # Merge dictionaries together. The second argument, "**json_dict" will take precedence and overwrite any duplicate parameters. update_dict = {**update_dict, **json_dict} if update_dict: phantom.debug( 'Updating container {0} with the following information: "{1}"'. format(container['id'], update_dict)) phantom.update(container, update_dict) else: phantom.debug( "Valid container entered but no valid container changes provided.") # Return a JSON-serializable object assert json.dumps( outputs ) # Will raise an exception if the :outputs: object is not JSON-serializable return outputs
def protect_prompt(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs): phantom.debug("protect_prompt() called") # set user and message variables for phantom.prompt call user_id = phantom.get_effective_user() url = phantom.build_phantom_rest_url('ph_user', user_id) response = phantom.requests.get(url, verify=False).json() user = response['username'] message = """{0}""" # parameter list for template variable replacement parameters = ["format_prompt:formatted_data"] device_data = phantom.collect2( container=container, datapath=['run_asset_query:action_result.data.*.nt_host'], action_results=results) device_list = [item[0] for item in device_data] user_data = phantom.collect2( container=container, datapath=['run_identity_query:action_result.data.*.email'], action_results=results) user_list = [item[0] for item in user_data] list_asset_playbooks_data = phantom.collect2( container=container, datapath=[ "list_asset_playbooks:custom_function_result.data.*.full_name" ]) list_asset_playbooks_list = [ item[0] for item in list_asset_playbooks_data if item[0] ] list_identity_playbooks_data = phantom.collect2( container=container, datapath=[ "list_identity_playbooks:custom_function_result.data.*.full_name" ]) list_identity_playbooks_list = [ item[0] for item in list_identity_playbooks_data if item[0] ] #responses: all_entity_list = [] response_types = [] # only add a response if a device exists and a playbook exists if device_list and list_asset_playbooks_list: for item in device_list: if item: response_types.append({ "prompt": "Launch protect asset playbooks on '{}'?".format(item), "options": { "type": "list", "choices": ["Yes", "No"] }, }) all_entity_list.append({'type': 'device', 'name': item}) # only add a response if a user exists and a playbook exists if user_list and list_identity_playbooks_list: for item in user_list: if item: response_types.append({ "prompt": "Launch protect identity playbooks on '{}'?".format(item), "options": { "type": "list", "choices": ["Yes", "No"] }, }) all_entity_list.append({'type': 'user', 'name': item}) phantom.save_run_data(key='all_entities', value=json.dumps(all_entity_list)) phantom.prompt2(container=container, user=user, message=message, respond_in_mins=30, name="protect_prompt", parameters=parameters, response_types=response_types, callback=decide_and_launch_playbooks) return
def workbook_task_update(task_name=None, note_title=None, note_content=None, status=None, owner=None, container=None, **kwargs): """ Update a workbook task by task name Args: task_name (CEF type: *): Name of a workbook task (Required) note_title (CEF type: *): Note title goes here (Optional) note_content (CEF type: *): Body of note goes here (Optional) status (CEF type: *): One of: incomplete, in_progress, complete (Optional) owner (CEF type: *): Assigns task to provided owner. Accepts keyword 'current" to assign task to currently running playbook user. (Optional) container (CEF type: phantom container id): ID of Phantom Container (Required) Returns a JSON-serializable object that implements the configured data paths: note_id: Returns note_id if a note was added """ ############################ Custom Code Goes Below This Line ################################# import json import phantom.rules as phantom outputs = {} # Ensure valid container input if isinstance(container, dict) and container.get('id'): container_id = container['id'] elif isinstance(container, int): container_id = container else: raise TypeError( "The input 'container' is neither a container dictionary nor an int, so it cannot be used" ) if task_name: task_list = phantom.get_tasks(container_id) task_count = 0 for task in task_list: if task_name == task['data']['name']: task_count += 1 if task_count > 1: raise RuntimeError( f'Unable to update workbook task - multiple tasks match criteria: {task_count}' ) task_id = task['data']['id'] task_is_note_required = task['data']['is_note_required'] task_count += 1 task_status = task['data']['status'] task_notes = task['data']['notes'] task_owner = task['data']['owner'] if task_count == 0: raise RuntimeError( f"No task name matches input task_name: '{task_name}'") if task_is_note_required and ( not note_content or not note_title) and status == 'complete' and task_status != 1: raise RuntimeError( 'Unable to update workbook task - The task requires a closing note and a closing title' ) else: # Add Note if note_content: success, message, note_id = phantom.add_note( container=container_id, note_type='task', task_id=task_id, title=note_title, content=note_content, note_format='markdown') outputs['note_id'] = str(note_id) # Set owner if owner: owner_dict = {} # If keyword 'current' entered then translate effective_user id to a username if owner.lower() == 'current': owner_dict['owner_id'] = phantom.get_effective_user() else: # Attempt to translate name to owner_id url = phantom.build_phantom_rest_url( 'ph_user') + f'?_filter_username="******"' data = phantom.requests.get(url, verify=False).json().get('data') if data and len(data) == 1: owner_dict['owner_id'] = data[0]['id'] elif data and len(data) > 1: raise RuntimeError(f'Multiple matches for owner "{owner}"') else: # Attempt to translate name to role_id url = phantom.build_phantom_rest_url( 'role') + f'?_filter_name="{owner}"' data = phantom.requests.get( url, verify=False).json().get('data') if data and len(data) == 1: owner_dict['role_id'] = data[0]['id'] elif data and len(data) > 1: raise RuntimeError( f'Multiple matches for owner "{owner}"') else: raise RuntimeError( f'"{owner}" is not a valid username or role') url = phantom.build_phantom_rest_url( 'workbook_task') + '/{}'.format(task_id) response = phantom.requests.post(url, data=json.dumps(owner_dict), verify=False).json() if not response.get('success'): raise RuntimeError(f'Error setting "{owner}" - {response}') # Set Status if isinstance(status, str): status = status.lower() url = phantom.build_phantom_rest_url( 'workbook_task') + '/{}'.format(task_id) if status == 'complete' and task_status == 0: # Move to in progress data = {'status': 2} response = phantom.requests.post(url, data=json.dumps(data), verify=False).json() if not response.get('success'): raise RuntimeError( f'Error setting status "{status}" - {response}') # Then move to close data = {'status': 1} if task_is_note_required and note_content: data['note'] = note_content data['title'] = note_title data['note_format'] = 'markdown' response = phantom.requests.post(url, data=json.dumps(data), verify=False).json() if not response.get('success'): raise RuntimeError( f'Error setting status "{status}" - {response}') elif (status == 'in progress' or status == 'in_progress') and task_status != 2: data = {'status': 2} # Move to in progress response = phantom.requests.post(url, data=json.dumps(data), verify=False).json() if not response.get('success'): raise RuntimeError( f'Error setting status "{status}" - {response}') elif status == 'incomplete' and task_status != 0: data = {'status': 0} # Move to incomplete response = phantom.requests.post(url, data=json.dumps(data), verify=False).json() if not response.get('success'): raise RuntimeError( f'Error setting status "{status}" - {response}') elif status == 'complete' and task_status != 1: data = {'status': 1} # Move to complete if task_is_note_required and note_content: data['note'] = note_content data['title'] = note_title data['note_format'] = 'markdown' response = phantom.requests.post(url, data=json.dumps(data), verify=False).json() if not response.get('success'): raise RuntimeError( f'Error setting status "{status}" - {response}') # Return a JSON-serializable object assert json.dumps( outputs ) # Will raise an exception if the :outputs: object is not JSON-serializable return outputs