def _test_connectivity(self, param): action_result = self.add_action_result(phantom.ActionResult(dict(param))) ret_val, resp_json = self._make_slack_rest_call(action_result, consts.SLACK_AUTH_TEST, {}) if not ret_val: self.save_progress("Test Connectivity Failed") return ret_val action_result.add_data(resp_json) self.save_progress("Auth check to Slack passed. Configuring app for team, {}".format(resp_json.get('team', 'Unknown Team'))) bot_username = resp_json.get('user') bot_user_id = resp_json.get('user_id') self.save_progress("Got username, {0}, and user ID, {1}, for the bot".format(bot_username, bot_user_id)) self._state['bot_name'] = bot_username self._state['bot_id'] = bot_user_id self.save_progress("Test Connectivity Passed") return action_result.set_status(phantom.APP_SUCCESS)
def _list_channels(self, param): self.debug_print("param", param) action_result = self.add_action_result(phantom.ActionResult(dict(param))) limit = self._validate_integers(action_result, param.get("limit", consts.SLACK_DEFAULT_LIMIT), consts.SLACK_LIMIT_KEY) if limit is None: return action_result.get_status() ret_val, resp_json = self._paginator(action_result, consts.SLACK_LIST_CHANNEL, "channels", limit=limit) if not ret_val: return action_result.get_status() action_result.add_data(resp_json) channels = resp_json.get('channels', []) for chan in channels: name = chan.get('name', 'unknownchannel') chan['name'] = '#' + name action_result.set_summary({"num_public_channels": len(channels)}) return action_result.set_status(phantom.APP_SUCCESS)
def _restart_service(self, param): action_result = self.add_action_result(phantom.ActionResult(param)) return action_result.set_status( phantom.APP_ERROR, "This action has been deprecated. Please use the NetWitness Logs and Packets 'restart device' action instead." )
def _list_users(self, param): self.debug_print("param", param) action_result = self.add_action_result(phantom.ActionResult(dict(param))) limit = self._validate_integers(action_result, param.get("limit", consts.SLACK_DEFAULT_LIMIT), consts.SLACK_LIMIT_KEY) if limit is None: return action_result.get_status() ret_val, resp_json = self._paginator(action_result, consts.SLACK_USER_LIST, "members", limit=limit) if not ret_val: return action_result.get_status() action_result.add_data(resp_json) users = resp_json.get('members', []) for user in users: name = user.get('name', 'unknownuser') user['name'] = '@' + name action_result.set_summary({"num_users": len(users)}) return action_result.set_status(phantom.APP_SUCCESS)
def _add_reaction(self, param): action_result = self.add_action_result( phantom.ActionResult(dict(param))) emoji = self._handle_py_ver_compat_for_input_str(param['emoji']) params = { 'channel': param['destination'], 'name': emoji, 'timestamp': param['message_ts'] } ret_val, resp_json = self._make_slack_rest_call( action_result, SLACK_ADD_REACTION, params) if not ret_val: message = action_result.get_message() if message: error_message = "{}: {}".format(SLACK_ERR_ADDING_REACTION, message) else: error_message = SLACK_ERR_ADDING_REACTION return action_result.set_status(phantom.APP_ERROR, error_message) action_result.add_data(resp_json) return action_result.set_status(phantom.APP_SUCCESS, SLACK_SUCC_REACTION_ADDED)
def _get_user(self, param): self.debug_print("param", param) action_result = self.add_action_result(phantom.ActionResult(dict(param))) user_id = param['user_id'] if not user_id.startswith('U'): return action_result.set_status(phantom.APP_ERROR, "The user parameter must be a user ID") ret_val, resp_json = self._make_slack_rest_call(action_result, consts.SLACK_USER_INFO, {'user': user_id}) if not ret_val: return phantom.APP_ERROR action_result.add_data(resp_json) user = resp_json.get('user') if not user: return action_result.set_status(phantom.APP_ERROR, "User data not found in json output") name = user.get('name', '') user['name'] = '@' + name return action_result.set_status(phantom.APP_SUCCESS, "User data successfully retrieved")
def _get_response(self, param): action_result = self.add_action_result( phantom.ActionResult(dict(param))) qid = self._handle_py_ver_compat_for_input_str(param['question_id']) state_dir = self.get_state_dir() answer_path = '{0}/{1}.json'.format(state_dir, qid) self.save_progress( 'Checking for response to question with ID: {0}'.format(qid)) try: answer_file = open(answer_path, 'r') except: return action_result.set_status( phantom.APP_ERROR, SLACK_ERR_QUESTION_RESPONSE_NOT_AVAILABLE) try: resp_json = json.loads(answer_file.read()) answer_file.close() except: return action_result.set_status( phantom.APP_ERROR, SLACK_ERR_UNABLE_TO_PARSE_RESPONSE) action_result.add_data(resp_json) action_result.set_summary({ 'response_received': True, 'response': resp_json.get("actions", [{}])[0].get("value") }) return action_result.set_status(phantom.APP_SUCCESS)
def _send_message(self, param): self.debug_print("param", param) action_result = self.add_action_result(phantom.ActionResult(dict(param))) message = self._handle_py_ver_compat_for_input_str(param['message']) if '\\' in message: if self._python_version == 2: message = message.decode('string_escape') else: message = bytes(message, "utf-8").decode("unicode_escape") if len(message) > consts.SLACK_MESSAGE_LIMIT: return (action_result.set_status(phantom.APP_ERROR, "Message too long. Please limit messages to {0} characters.".format(consts.SLACK_MESSAGE_LIMIT))) params = {'channel': param['destination'], 'text': message, 'as_user': True} ret_val, resp_json = self._make_slack_rest_call(action_result, consts.SLACK_SEND_MESSAGE, params) if not ret_val: return ret_val action_result.add_data(resp_json) return action_result.set_status(phantom.APP_SUCCESS, "Message sent successfully")
def _list_events(self, param): self.debug_print(param) action_result = self.add_action_result(phantom.ActionResult(param)) alert_id = param['id'] limit = int(param.get('limit', consts.RSASA_DEFAULT_EVENT_LIMIT)) ret_val, events = self._get_events(action_result, alert_id, limit) if not ret_val: return ret_val for event in events: if event['data'][0]['filename'] and not event['data'][0]['hash']: self._extract_device_and_hash(event) action_result.add_data(events) action_result.set_summary({"num_events": len(events)}) for event in events: for link in event['related_links']: if link['type'] == 'investigate_original_event': event['id'] = link['url'].split('/')[-1] return action_result.set_status(phantom.APP_SUCCESS)
def _list_devices(self, param): self.debug_print(param) action_result = self.add_action_result(phantom.ActionResult(param)) endpoint = '/common/devices' query_params = {'page': 1, 'start': 0, 'limit': 0} ret_val, data = self._make_rest_call(endpoint, action_result, params=query_params) if not ret_val: return ret_val devices = data.get('data') if not data: return action_result.set_status(phantom.APP_ERROR, consts.RSASA_ERR_NO_DEVICES) action_result.add_data(devices) action_result.set_summary({'num_devices': len(devices)}) return action_result.set_status(phantom.APP_SUCCESS)
def _invite_users(self, param): action_result = self.add_action_result( phantom.ActionResult(dict(param))) user_token = self.get_config().get('user_token') if not user_token: return action_result.set_status(phantom.APP_ERROR, SLACK_ERR_USER_TOKEN_NOT_PROVIDED) headers = { "Authorization": "Bearer {}".format(user_token), 'Content-Type': 'application/json' } users = [x.strip() for x in param['users'].split(',')] users = list(filter(None, users)) if not users: return action_result.set_status(phantom.APP_ERROR, SLACK_ERR_INVALID_USER) params = { 'users': users, 'channel': param['channel_id'], 'token': user_token } endpoint = "{}{}".format(SLACK_BASE_URL, SLACK_INVITE_TO_CHANNEL) ret_val, resp_json = self._make_rest_call(action_result, endpoint, False, method=requests.post, headers=headers, body=params) if not ret_val: return ret_val if not resp_json.get('ok', True): error = resp_json.get('error', 'N/A') error_details = self._handle_py_ver_compat_for_input_str( resp_json.get('detail', '')) if error_details: error_message = "{}: {}\r\nDetails: {}".format( SLACK_ERR_INVITING_CHANNEL, error, error_details) else: error_message = "{}: {}".format(SLACK_ERR_INVITING_CHANNEL, error) return action_result.set_status(phantom.APP_ERROR, error_message) action_result.add_data(resp_json) return action_result.set_status(phantom.APP_SUCCESS, SLACK_SUCC_INVITE_SENT)
def _create_channel(self, param): action_result = self.add_action_result( phantom.ActionResult(dict(param))) user_token = self.get_config().get('user_token') if not user_token: return action_result.set_status(phantom.APP_ERROR, SLACK_ERR_USER_TOKEN_NOT_PROVIDED) headers = { "Authorization": "Bearer {}".format(user_token), 'Content-Type': 'application/json' } params = {'name': param['name'], 'token': user_token, 'validate': True} endpoint = "{}{}".format(SLACK_BASE_URL, SLACK_CHANNEL_CREATE_ENDPOINT) # private channel channel_type = param.get("channel_type", "public") if channel_type not in ["public", "private"]: return action_result.set_status(phantom.APP_ERROR, SLACK_ERR_INVALID_CHANNEL_TYPE) if channel_type == "private": params.update({"is_private": True}) ret_val, resp_json = self._make_rest_call(action_result, endpoint, False, method=requests.post, headers=headers, body=params) if not ret_val: return ret_val if not resp_json.get('ok', True): error = resp_json.get('error', 'N/A') error_details = self._handle_py_ver_compat_for_input_str( resp_json.get('detail', '')) if error_details: error_message = "{}: {}\r\nDetails: {}".format( SLACK_ERR_CREATING_CHANNEL, error, error_details) else: error_message = "{}: {}".format(SLACK_ERR_CREATING_CHANNEL, error) return action_result.set_status(phantom.APP_ERROR, error_message) action_result.add_data(resp_json) return action_result.set_status(phantom.APP_SUCCESS, SLACK_SUCC_CHANNEL_CREATED)
def _stop_bot(self, param): action_result = self.add_action_result(phantom.ActionResult(dict(param))) pid = self._state.get('pid', '') if pid: self._state.pop('pid') try: if 'slack_bot.pyc' in sh.ps('ww', pid): # pylint: disable=E1101 sh.kill(pid) # pylint: disable=E1101 action_result.set_status(phantom.APP_SUCCESS, "SlackBot has been stopped.") except: action_result.set_status(phantom.APP_SUCCESS, "SlackBot isn't running, not going to stop it.") else: action_result.set_status(phantom.APP_SUCCESS, "SlackBot isn't running, not going to stop it.") rest_url = consts.SLACK_PHANTOM_ASSET_INFO_URL.format(url=self.get_phantom_base_url(), asset_id=self.get_asset_id()) ret_val, resp_json = self._make_rest_call(action_result, rest_url, False) if phantom.is_fail(ret_val): return ret_val asset_config = resp_json.get('configuration', {}) ingest_config = asset_config.get('ingest', {}) poll = ingest_config.get('poll') if poll is None: return action_result.set_status(phantom.APP_SUCCESS, "{} Failed to disable ingestion, please check that ingest settings are correct." .format(action_result.get_message())) if not poll: return action_result.set_status(phantom.APP_SUCCESS, "{} Ingestion isn't enabled, not going to disable it.".format(action_result.get_message())) ingest_config['poll'] = False body = {'configuration': asset_config} ret_val, resp_json = self._make_rest_call(action_result, rest_url, False, method=requests.post, body=body) if phantom.is_fail(ret_val): return ret_val return action_result.set_status(phantom.APP_SUCCESS, "{} Ingestion has been disabled.".format(action_result.get_message()))
def _on_poll(self, param): """ Keep phantom containers up-to-date with data from redmine """ # Add action result action_result = self.add_action_result(phantom.ActionResult(dict(param))) last_run = self._state.get("last_run") max_tickets = None backfill = datetime.now() - timedelta(REDMINE_BACKFILL_DAYS) if self.is_poll_now(): self.debug_print("Run Mode: Poll Now") # Integer validation for 'maximum containers' configuration parameter max_tickets = param[phantom.APP_JSON_CONTAINER_COUNT] ret_val, max_tickets = self._validate_integer(action_result, max_tickets, REDMINE_CONTAINER_COUNT_KEY) if phantom.is_fail(ret_val): return action_result.get_status() last_run = backfill else: if not last_run: self.debug_print("Run Mode: First Scheduled Poll") last_run = backfill else: self.debug_print("Run Mode: Scheduled Poll") last_run = dateutil.parser.isoparse(last_run) self.debug_print(f"Last Run: {last_run}") tickets = [] try: ret_val, tickets = self._retrieve_tickets( action_result, last_run, max_tickets ) if phantom.is_fail(ret_val): return action_result.get_status() except Exception as e: error_msg = self._get_error_message_from_exception(e) return action_result.set_status( phantom.APP_ERROR, REDMINE_ERR_FETCHING_TICKETS.format(error=error_msg) ) self.debug_print(f"Total tickets fetched: {len(tickets)}") self.save_progress(f"Total tickets fetched: {len(tickets)}") for ticket in tickets: self._save_ticket_container(action_result, ticket) return action_result.set_status(phantom.APP_SUCCESS)
def _list_alerts(self, param): self.debug_print(param) action_result = self.add_action_result(phantom.ActionResult(param)) incident_id = param.get('id') limit = int(param.get('limit', consts.RSASA_DEFAULT_ALERT_LIMIT)) ret_val, alerts = self._get_alerts(action_result, incident_id, limit) if not ret_val: return ret_val action_result.add_data(alerts) action_result.set_summary({"num_alerts": len(alerts)}) return action_result.set_status(phantom.APP_SUCCESS)
def _invite_users(self, param): action_result = self.add_action_result(phantom.ActionResult(dict(param))) user_token = self.get_config().get('user_token') if not user_token: return action_result.set_status(phantom.APP_ERROR, "user_token is required for this action. Navigate to the asset's configuration and add a token now and try again.") headers = { "Authorization": "Bearer " + user_token, 'Content-Type': 'application/json' } users = [x.strip() for x in param['users'].split(',')] users = list(filter(None, users)) params = { 'users': users, 'channel': param['channel_id'], 'token': user_token } endpoint = consts.SLACK_BASE_URL + consts.SLACK_INVITE_TO_CHANNEL ret_val, resp_json = self._make_rest_call( action_result, endpoint, False, method=requests.post, headers=headers, body=params ) if not ret_val: return ret_val if not resp_json.get('ok', True): return action_result.set_status( phantom.APP_ERROR, "Error inviting to channel: {}\r\nDetails: {}".format(self._handle_py_ver_compat_for_input_str(resp_json.get('error', 'N/A')), self._handle_py_ver_compat_for_input_str(resp_json.get('detail', ''))) ) action_result.add_data(resp_json) return action_result.set_status(phantom.APP_SUCCESS, "Invite sent to user(s)")
def _create_channel(self, param): action_result = self.add_action_result(phantom.ActionResult(dict(param))) user_token = self.get_config().get('user_token') if not user_token: return action_result.set_status(phantom.APP_ERROR, "user_token is required for this action. Navigate to the asset's configuration and add a token now and try again.") headers = { "Authorization": "Bearer " + user_token, 'Content-Type': 'application/json' } params = { 'name': param['name'], 'token': user_token, 'validate': True } endpoint = consts.SLACK_BASE_URL + consts.SLACK_CHANNEL_CREATE_ENDPOINT # private channel if param['channel_type'] == "private": params.update({"is_private": True}) ret_val, resp_json = self._make_rest_call( action_result, endpoint, False, method=requests.post, headers=headers, body=params ) if not ret_val: return ret_val if not resp_json.get('ok', True): return action_result.set_status( phantom.APP_ERROR, "Error creating channel: {}\r\nDetails: {}".format(self._handle_py_ver_compat_for_input_str(resp_json.get('error', 'N/A')), self._handle_py_ver_compat_for_input_str(resp_json.get('detail', ''))) ) action_result.add_data(resp_json) return action_result.set_status(phantom.APP_SUCCESS)
def _list_incidents(self, param): self.debug_print(param) action_result = self.add_action_result(phantom.ActionResult(param)) max_incidents = param.get('limit', consts.RSASA_DEFAULT_INCIDENT_LIMIT) start_time = param.get('start_time') end_time = param.get('end_time') epoch = datetime.utcfromtimestamp(0) end_epoch = 0 start_epoch = 0 if start_time or end_time: try: if start_time: start_epoch = int( (datetime.strptime(start_time, "%Y-%m-%d %H:%M:%S") - epoch).total_seconds() * 1000) else: start_epoch = consts.RSASA_DEFAULT_START_TIME if end_time: end_epoch = int( (datetime.strptime(end_time, "%Y-%m-%d %H:%M:%S") - epoch).total_seconds() * 1000) else: end_epoch = int(time.time() * 1000) except ValueError as e: return action_result.set_status(phantom.APP_ERROR, str(e)) ret_val, incidents = self._get_incidents(action_result, max_incidents, start_epoch, end_epoch, sort='DESC') if not ret_val: return ret_val action_result.add_data(incidents) action_result.set_summary({"num_incidents": len(incidents)}) return action_result.set_status(phantom.APP_SUCCESS)
def _send_message(self, param): action_result = self.add_action_result( phantom.ActionResult(dict(param))) message = self._handle_py_ver_compat_for_input_str(param['message']) if '\\' in message: if self._python_version == 2: message = message.decode('string_escape') else: message = bytes(message, "utf-8").decode("unicode_escape") if len(message) > SLACK_MESSAGE_LIMIT: return action_result.set_status( phantom.APP_ERROR, SLACK_ERR_MESSAGE_TOO_LONG.format(limit=SLACK_MESSAGE_LIMIT)) params = {'channel': param['destination'], 'text': message} if 'parent_message_ts' in param: # Support for replying in thread params['thread_ts'] = param.get('parent_message_ts') if 'reply_broadcast' in param: params['reply_broadcast'] = param.get('reply_broadcast', False) ret_val, resp_json = self._make_slack_rest_call( action_result, SLACK_SEND_MESSAGE, params) if not ret_val: message = action_result.get_message() if message: error_message = "{}: {}".format(SLACK_ERR_SENDING_MESSAGE, message) else: error_message = SLACK_ERR_SENDING_MESSAGE return action_result.set_status(phantom.APP_ERROR, error_message) action_result.add_data(resp_json) return action_result.set_status(phantom.APP_SUCCESS, SLACK_SUCC_MESSAGE_SENT)
def _get_user(self, param): self.debug_print("param", param) action_result = self.add_action_result( phantom.ActionResult(dict(param))) user_id = param['user_id'] if not user_id.startswith('U'): return action_result.set_status(phantom.APP_ERROR, SLACK_ERR_NOT_A_USER_ID) ret_val, resp_json = self._make_slack_rest_call( action_result, SLACK_USER_INFO, {'user': user_id}) if not ret_val: message = action_result.get_message() if message: error_message = "{}: {}".format(SLACK_ERR_FETCHING_USER, message) else: error_message = SLACK_ERR_FETCHING_USER return action_result.set_status(phantom.APP_ERROR, error_message) action_result.add_data(resp_json) user = resp_json.get('user') if not user: return action_result.set_status( phantom.APP_ERROR, SLACK_ERR_DATA_NOT_FOUND_IN_OUTPUT.format(key="User")) name = user.get('name', '') user['name'] = '@{}'.format(name) return action_result.set_status(phantom.APP_SUCCESS, SLACK_SUCC_USER_DATA_RETRIEVED)
def _get_response(self, param): action_result = self.add_action_result(phantom.ActionResult(dict(param))) qid = self._handle_py_ver_compat_for_input_str(param['question_id']) state_dir = self.get_state_dir() answer_path = '{0}/{1}.json'.format(state_dir, qid) self.save_progress('Checking for response to question with ID: {0}'.format(qid)) try: answer_file = open(answer_path, 'r') except: return action_result.set_status(phantom.APP_ERROR, "Response to question not available") try: resp_json = json.loads(answer_file.read()) except: return action_result.set_status(phantom.APP_ERROR, "Error occurred while parsing the response") action_result.add_data(resp_json) action_result.set_summary({'response_received': True, 'response': resp_json.get("actions", [{}])[0].get("value")}) return action_result.set_status(phantom.APP_SUCCESS)
def _on_poll(self, param): action_result = self.add_action_result(phantom.ActionResult(param)) config = self.get_config() # Get the maximum number of tickets that we can poll, same as container count if self.is_poll_now(): try: max_containers = int(param[phantom.APP_JSON_CONTAINER_COUNT]) except: return action_result.set_status(phantom.APP_ERROR, "Invalid Container count") else: max_containers = config['max_incidents'] start_time, end_time = self._get_time_range() self.save_progress( "Getting incident IDs generated\nFrom: {0}\nTo: {1}".format( time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(start_time / 1000)), time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(end_time / 1000)))) # get the incidents ret_val, incidents = self._get_incidents(action_result, max_containers, start_time, end_time) if phantom.is_fail(ret_val): return action_result.get_status() if not incidents: return action_result.set_status(phantom.APP_SUCCESS, consts.RSASA_NO_INCIDENTS) for incident in incidents: ret_val, alerts = self._get_alerts(action_result, incident.get('id'), 0) incident['alerts'] = alerts if phantom.is_fail(ret_val): self.debug_print("get alert failed with: {0}".format( action_result.get_message())) for alert in alerts: ret_val, events = self._get_events(action_result, alert.get('id'), 0) alert['events'] = events if phantom.is_fail(ret_val): self.debug_print("get event failed with: {0}".format( action_result.get_message())) for event in events: self._extract_device_and_hash(event) self.save_progress("Got {0} incidents".format(len(incidents))) if not self.is_poll_now(): if len(incidents) == int(max_containers): self._state[ consts. RSASA_JSON_LAST_DATE_TIME] = incidents[-1]['created'] + 1 else: self._state[consts.RSASA_JSON_LAST_DATE_TIME] = end_time + 1 results = pi.parse_incidents(incidents, self) self._parse_results(action_result, param, results) # blank line to update the last status message self.send_progress('') return action_result.set_status(phantom.APP_SUCCESS)
def _upload_file(self, param): self.debug_print("param", param) action_result = self.add_action_result(phantom.ActionResult(dict(param))) caption = param.get('caption', '') if caption: caption += ' -- ' caption += 'Uploaded from Phantom' vault_id = param['file'] # check the vault for a file with the supplied ID try: file_info = Vault.get_file_info(vault_id) if not file_info: return action_result.set_status(phantom.APP_ERROR, consts.SLACK_ERR_UNABLE_TO_FETCH_FILE.format(key="info")) file_path = file_info[0].get("path") if not file_path: return action_result.set_status(phantom.APP_ERROR, consts.SLACK_ERR_UNABLE_TO_FETCH_FILE.format(key="path")) file_name = file_info[0].get("name") if not file_name: return action_result.set_status(phantom.APP_ERROR, consts.SLACK_ERR_UNABLE_TO_FETCH_FILE.format(key="name")) except: return action_result.set_status(phantom.APP_ERROR, "Could not find the specified Vault ID in vault") upfile = open(file_path, 'rb') params = {'channels': param['destination'], 'initial_comment': caption, 'filename': file_name} ret_val, resp_json = self._make_slack_rest_call(action_result, consts.SLACK_UPLOAD_FILE, params, files={'file': upfile}) if not ret_val: return ret_val file_json = resp_json.get('file', {}) thumbnail_dict = {} pop_list = [] for key, value in list(file_json.items()): if key.startswith('thumb'): pop_list.append(key) name_arr = key.split('_') thumb_name = "{0}_{1}".format(name_arr[0], name_arr[1]) if thumb_name not in thumbnail_dict: thumbnail_dict[thumb_name] = {} thumb_dict = thumbnail_dict[thumb_name] if len(name_arr) == 2: thumb_dict['img_url'] = value elif name_arr[2] == 'w': thumb_dict['width'] = value elif name_arr[2] == 'h': thumb_dict['height'] = value elif key == 'initial_comment': resp_json['caption'] = value pop_list.append(key) elif key in ['channels', 'ims', 'groups']: if 'destinations' not in resp_json: resp_json['destinations'] = [] resp_json['destinations'] += value pop_list.append(key) elif key == 'username': pop_list.append(key) elif key == 'user': resp_json['sender'] = value pop_list.append(key) for poppee in pop_list: file_json.pop(poppee) resp_json['thumbnails'] = thumbnail_dict action_result.add_data(resp_json) return action_result.set_status(phantom.APP_SUCCESS, "File uploaded successfully.")
def _ask_question(self, param): action_result = self.add_action_result( phantom.ActionResult(dict(param))) config = self.get_config() local_data_state_dir = self.get_state_dir().rstrip('/') self._state['local_data_path'] = local_data_state_dir # Need to make sure the configured verification token is in the app state so the request_handler can use it to verify POST requests if 'token' not in self._state: self._state['token'] = config[SLACK_JSON_VERIFICATION_TOKEN] self.save_state(self._state) elif self._state['token'] != config[SLACK_JSON_VERIFICATION_TOKEN]: self._state['token'] = config[SLACK_JSON_VERIFICATION_TOKEN] self.save_state(self._state) # The default permission of state file in Phantom v4.9 is 600. So when from rest handler method (handle_request) reads this state file, # the action fails with "permission denied" error message # Adding the data of state file to another temporary file to resolve this issue _save_app_state(self._state, self.get_asset_id(), self) question = param['question'] if len(question) > SLACK_MESSAGE_LIMIT: return action_result.set_status( phantom.APP_ERROR, SLACK_ERR_QUESTION_TOO_LONG.format(limit=SLACK_MESSAGE_LIMIT)) user = param['destination'] if user.startswith('#') or user.startswith('C'): # Don't want to send question to channels because then we would not know who was answering return action_result.set_status( phantom.APP_ERROR, SLACK_ERR_UNABLE_TO_SEND_QUESTION_TO_CHANNEL) qid = uuid.uuid4().hex answer_filename = '{0}.json'.format(qid) answer_path = "{0}/{1}".format(local_data_state_dir, answer_filename) path_json = { 'qid': qid, 'asset_id': str(self.get_asset_id()), 'confirmation': param.get('confirmation', ' ') } callback_id = json.dumps(path_json) if len(callback_id) > 255: path_json['confirmation'] = '' valid_length = 255 - len(json.dumps(path_json)) return action_result.set_status( phantom.APP_ERROR, SLACK_ERR_LENGTH_LIMIT_EXCEEDED.format( asset_length=len(self.get_asset_id()), valid_length=valid_length)) self.save_progress('Asking question with ID: {0}'.format(qid)) answers = [] given_answers = [ x.strip() for x in param.get('responses', 'yes,no').split(',') ] given_answers = list(filter(None, given_answers)) for answer in given_answers: answer_json = { 'name': answer, 'text': answer, 'value': answer, 'type': 'button' } answers.append(answer_json) answer_json = [{ 'text': question, 'fallback': 'Phantom cannot post questions on this channel.', 'callback_id': callback_id, 'color': '#422E61', 'attachment_type': 'default', 'actions': answers }] params = { 'channel': user, 'attachments': json.dumps(answer_json), 'as_user': True } ret_val, resp_json = self._make_slack_rest_call( action_result, SLACK_SEND_MESSAGE, params) if not ret_val: message = action_result.get_message() if message: error_message = "{}: {}".format(SLACK_ERR_ASKING_QUESTION, message) else: error_message = SLACK_ERR_ASKING_QUESTION return action_result.set_status(phantom.APP_ERROR, error_message) loop_count = (self._timeout * 60) / self._interval count = 0 while True: if count >= loop_count: action_result.set_summary({ 'response_received': False, 'question_id': qid }) return action_result.set_status(phantom.APP_SUCCESS) try: answer_file = open(answer_path, 'r') except: count += 1 time.sleep(self._interval) continue try: resp_json = json.loads(answer_file.read()) answer_file.close() except: return action_result.set_status( phantom.APP_ERROR, SLACK_ERR_UNABLE_TO_PARSE_RESPONSE) break action_result.add_data(resp_json) action_result.set_summary({ 'response_received': True, 'question_id': qid, 'response': resp_json.get("actions", [{}])[0].get("value") }) os.remove(answer_path) return action_result.set_status(phantom.APP_SUCCESS)
def _upload_file(self, param): action_result = self.add_action_result( phantom.ActionResult(dict(param))) caption = param.get('caption', '') if caption: caption += ' -- ' caption += 'Uploaded from Phantom' kwargs = {} params = {'channels': param['destination'], 'initial_comment': caption} if 'filetype' in param: params['filetype'] = param.get('filetype') if 'filename' in param: params['filename'] = param.get('filename') if 'parent_message_ts' in param: # Support for replying in thread params['thread_ts'] = param.get('parent_message_ts') if 'file' in param: vault_id = param.get('file') # check the vault for a file with the supplied ID try: success, message, vault_meta_info = ph_rules.vault_info( vault_id=vault_id) vault_meta_info = list(vault_meta_info) if not success or not vault_meta_info: error_msg = " Error Details: {}".format( unquote(message)) if message else '' return action_result.set_status( phantom.APP_ERROR, "{}.{}".format( SLACK_ERR_UNABLE_TO_FETCH_FILE.format(key="info"), error_msg)) except Exception as e: err = self._get_error_message_from_exception(e) return action_result.set_status( phantom.APP_ERROR, "{}. {}".format( SLACK_ERR_UNABLE_TO_FETCH_FILE.format(key="info"), err)) # phantom vault file path file_path = vault_meta_info[0].get('path') if not file_path: return action_result.set_status( phantom.APP_ERROR, SLACK_ERR_UNABLE_TO_FETCH_FILE.format(key="path")) # phantom vault file name file_name = vault_meta_info[0].get('name') if not file_name: return action_result.set_status( phantom.APP_ERROR, SLACK_ERR_UNABLE_TO_FETCH_FILE.format(key="name")) upfile = open(file_path, 'rb') params['filename'] = file_name kwargs['files'] = {'file': upfile} elif 'content' in param: params['content'] = self._handle_py_ver_compat_for_input_str( param.get('content')) else: return action_result.set_status( phantom.APP_ERROR, SLACK_ERR_FILE_OR_CONTENT_NOT_PROVIDED) ret_val, resp_json = self._make_slack_rest_call( action_result, SLACK_UPLOAD_FILE, params, **kwargs) if 'files' in kwargs: upfile.close() if not ret_val: message = action_result.get_message() if message: error_message = "{}: {}".format(SLACK_ERR_UPLOADING_FILE, message) else: error_message = SLACK_ERR_UPLOADING_FILE return action_result.set_status(phantom.APP_ERROR, error_message) file_json = resp_json.get('file', {}) thumbnail_dict = {} pop_list = [] for key, value in list(file_json.items()): if key.startswith('thumb'): pop_list.append(key) name_arr = key.split('_') thumb_name = "{0}_{1}".format(name_arr[0], name_arr[1]) if thumb_name not in thumbnail_dict: thumbnail_dict[thumb_name] = {} thumb_dict = thumbnail_dict[thumb_name] if len(name_arr) == 2: thumb_dict['img_url'] = value elif name_arr[2] == 'w': thumb_dict['width'] = value elif name_arr[2] == 'h': thumb_dict['height'] = value elif key == 'initial_comment': resp_json['caption'] = value pop_list.append(key) elif key in ['channels', 'ims', 'groups']: if 'destinations' not in resp_json: resp_json['destinations'] = [] resp_json['destinations'] += value pop_list.append(key) elif key == 'username': pop_list.append(key) elif key == 'user': resp_json['sender'] = value pop_list.append(key) for poppee in pop_list: file_json.pop(poppee) resp_json['thumbnails'] = thumbnail_dict action_result.add_data(resp_json) return action_result.set_status(phantom.APP_SUCCESS, SLACK_SUCC_FILE_UPLOAD)
def _handle_get_pcap(self, param): """ Request PCAP download, get status, and save to disk. Args: param (dict): Parameters sent in by a user or playbook Returns: ActionResult: * str: status success/failure * str: message """ action_result = self.add_action_result( phantom.ActionResult(dict(param))) summary_data = action_result.update_summary( {'pcap_id': param['pcap_id']}) endpoint = "datamines/{}/download/download.pcap".format( param['pcap_id']) self.send_progress('Download starting') # Start download of PCAP file dl_ret_val, dl_response = self._make_rest_call(endpoint, action_result, method='get') # Check if something went wrong with the request if phantom.is_fail(dl_ret_val): return action_result.get_status() self.send_progress('Checking status of download') # Run get_status to check if the download fully completed or if it had been canceled endpoint = "datamines/{}".format(param['pcap_id']) status_ret_val, status_response = self._make_rest_call(endpoint, action_result, method='get') all_response = {'status': status_response} if phantom.is_fail(status_ret_val): return action_result.get_status() state = status_response.get('datamine', {}).get('status', {}).get('state', None) summary_data['state'] = state if state != 'COMPLETED': action_result.add_data(all_response) return action_result.set_status( phantom.APP_ERROR, 'Error downloading file. {}'.format(state)) filename = "{}.pcap".format(param['pcap_id']) self.send_progress('Saving file to disk') # Creating file temp_dir = tempfile.mkdtemp() try: file_path = os.path.join(temp_dir, filename) with open(file_path, 'wb') as file_obj: file_obj.write(dl_response) except Exception as e: self.debug_print('Error creating file') shutil.rmtree(temp_dir) err_msg = self._get_error_message_from_exception(e) return action_result.set_status( phantom.APP_ERROR, 'Error creating file. Error Details: {}'.format(err_msg)) container_id = self.get_container_id() # Check if file with same file name and size is available in vault and save only if it is not available try: vault_list = Vault.vault_info(container_id=container_id) except Exception as e: err_msg = self._get_error_message_from_exception(e) return action_result.set_status( phantom.APP_ERROR, 'Unable to get Vault item details from Phantom. Details: {0}'. format(err_msg)) vault_details = {} try: # Iterate through each vault item in the container and compare name and size of file for vault in vault_list[2]: if vault.get('name') == filename and vault.get( 'size') == os.path.getsize(file_path): self.send_progress('PCAP already available in Vault') vault_details = { phantom.APP_JSON_SIZE: vault.get('size'), phantom.APP_JSON_VAULT_ID: vault.get(phantom.APP_JSON_VAULT_ID), 'filename': filename } except Exception as e: err_msg = self._get_error_message_from_exception(e) return action_result.set_status( phantom.APP_ERROR, 'Error details: {}'.format(err_msg)) if not vault_details: vault_ret_val, vault_details = self._move_file_to_vault( container_id, os.path.getsize(file_path), 'pcap', file_path, action_result) # Check if something went wrong while moving file to vault if phantom.is_fail(vault_ret_val): return action_result.set_status( phantom.APP_ERROR, 'Could not move file to vault') shutil.rmtree(temp_dir) summary_data['file_availability'] = True summary_data[phantom.APP_JSON_VAULT_ID] = vault_details[ phantom.APP_JSON_VAULT_ID] all_response['vault'] = vault_details action_result.add_data(all_response) message = 'PCAP downloaded to Vault: {0}'.format( vault_details[phantom.APP_JSON_VAULT_ID]) return action_result.set_status(phantom.APP_SUCCESS, message)
def _on_poll(self, param): action_result = self.add_action_result( phantom.ActionResult(dict(param))) ret_val, resp_json = self._make_slack_rest_call( action_result, SLACK_AUTH_TEST, {}) if not ret_val: return ret_val bot_id = resp_json.get('user_id') if not bot_id: return action_result.set_status(phantom.APP_ERROR, SLACK_ERR_COULD_NOT_GET_BOT_ID) pid = self._state.get('pid', '') if pid: try: if 'slack_bot.pyc' in sh.ps('ww', pid): # pylint: disable=E1101 self.save_progress( "Detected SlackBot running with pid {0}".format(pid)) return action_result.set_status( phantom.APP_SUCCESS, SLACK_SUCC_SLACKBOT_RUNNING) except: pass config = self.get_config() bot_token = config.get('bot_token', '') ph_auth_token = config.get('ph_auth_token', None) if not ph_auth_token: return action_result.set_status(phantom.APP_ERROR, SLACK_ERR_AUTH_TOKEN_NOT_PROVIDED) app_version = self.get_app_json().get('app_version', '') try: ps_out = sh.grep(sh.ps('ww', 'aux'), 'slack_bot.pyc') # pylint: disable=E1101 if app_version not in ps_out: old_pid = shlex.split(str(ps_out))[1] self.save_progress( "Found an old version of slackbot running with pid {}, going to kill it" .format(old_pid)) sh.kill(old_pid) # pylint: disable=E1101 except: pass try: if bot_token in sh.grep(sh.ps('ww', 'aux'), 'slack_bot.pyc'): # pylint: disable=E1101 return action_result.set_status( phantom.APP_ERROR, SLACK_ERR_SLACKBOT_RUNNING_WITH_SAME_BOT_TOKEN) except: pass self.save_progress("Starting SlackBot") ret_val, base_url = self._get_phantom_base_url_slack(action_result) if phantom.is_fail(ret_val): return ret_val slack_bot_filename = os.path.join( os.path.dirname(os.path.realpath(__file__)), 'slack_bot.pyc') base_url += '/' if not base_url.endswith('/') else '' proc = subprocess.Popen([ 'phenv', 'python2.7', slack_bot_filename, bot_token, bot_id, base_url, app_version, ph_auth_token ]) self._state['pid'] = proc.pid self.save_progress("Started SlackBot with pid: {0}".format(proc.pid)) return action_result.set_status(phantom.APP_SUCCESS, SLACK_SUCC_SLACKBOT_STARTED)
def _on_poll(self, param): action_result = self.add_action_result(phantom.ActionResult(dict(param))) ret_val, resp_json = self._make_slack_rest_call(action_result, consts.SLACK_AUTH_TEST, {}) if not ret_val: return ret_val bot_id = resp_json.get('user_id') if not bot_id: return action_result.set_status(phantom.APP_ERROR, "Could not get bot ID from Slack") pid = self._state.get('pid', '') if pid: try: if 'slack_bot.pyc' in sh.ps('ww', pid): # pylint: disable=E1101 self.save_progress("Detected SlackBot running with pid {0}".format(pid)) return action_result.set_status(phantom.APP_SUCCESS, "SlackBot already running") except: pass config = self.get_config() bot_token = config.get('bot_token', '') ph_auth_token = config.get('ph_auth_token', None) if not ph_auth_token: return action_result.set_status(phantom.APP_ERROR, "The ph_auth_token asset configuration parameter is required to run the on_poll action.") app_version = self.get_app_json().get('app_version', '') try: ps_out = sh.grep(sh.ps('ww', 'aux'), 'slack_bot.pyc') # pylint: disable=E1101 if app_version not in ps_out: old_pid = shlex.split(str(ps_out))[1] self.save_progress("Found an old version of slackbot running with pid {}, going to kill it".format(old_pid)) sh.kill(old_pid) # pylint: disable=E1101 except: pass try: if bot_token in sh.grep(sh.ps('ww', 'aux'), 'slack_bot.pyc'): # pylint: disable=E1101 return action_result.set_status(phantom.APP_ERROR, "Detected an instance of SlackBot running with the same bot token. Not going to start new instance.") except: pass self.save_progress("Starting SlackBot") ret_val, base_url = self._get_phantom_base_url_slack(action_result) if phantom.is_fail(ret_val): return ret_val slack_bot_filename = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'slack_bot.pyc') base_url += '/' if not base_url.endswith('/') else '' proc = subprocess.Popen(['phenv', 'python2.7', slack_bot_filename, bot_token, bot_id, base_url, app_version, ph_auth_token]) self._state['pid'] = proc.pid self.save_progress("Started SlackBot with pid: {0}".format(proc.pid)) return action_result.set_status(phantom.APP_SUCCESS, "SlackBot started")
def _ask_question(self, param): action_result = self.add_action_result(phantom.ActionResult(dict(param))) config = self.get_config() # Need to make sure the configured verification token is in the app state so the request_handler can use it to verify POST requests if 'token' not in self._state: self._state['token'] = config[consts.SLACK_JSON_VERIFICATION_TOKEN] self.save_state(self._state) elif self._state['token'] != config[consts.SLACK_JSON_VERIFICATION_TOKEN]: self._state['token'] = config[consts.SLACK_JSON_VERIFICATION_TOKEN] self.save_state(self._state) # The default permission of state file in Phantom v4.9 is 600. So when from rest handler method (handle_request) reads this state file, # the action fails with "permission denied" error message # Adding the data of state file to another temporary file to resolve this issue _save_app_state(self._state, self.get_asset_id(), self) question = param['question'] if len(question) > consts.SLACK_MESSAGE_LIMIT: return action_result.set_status(phantom.APP_ERROR, "Question too long. Please limit questions to {0} characters.".format(consts.SLACK_MESSAGE_LIMIT)) user = param['destination'] if user.startswith('#') or user.startswith('C'): # Don't want to send question to channels because then we would not know who was answering return action_result.set_status(phantom.APP_ERROR, "Questions may only be sent as direct messages to users. They may not be sent to channels.") qid = uuid.uuid4().hex apps_state_dir = os.path.dirname(os.path.abspath(__file__)) local_data_state_dir = self.get_state_dir() state_dir = "{0},{1}".format(apps_state_dir, local_data_state_dir) answer_filename = '{0}.json'.format(qid) state_filename = "{0}_state.json".format(self.get_asset_id()) path_json = {'answer': answer_filename, 'state': state_filename, 'directory': state_dir, 'confirmation': param.get('confirmation', ' ')} self.save_progress('Asking question with ID: {0}'.format(qid)) answers = [] given_answers = [x.strip() for x in param.get('responses', 'yes,no').split(',')] given_answers = list(filter(None, given_answers)) for answer in given_answers: answer_json = {'name': answer, 'text': answer, 'value': answer, 'type': 'button'} answers.append(answer_json) answer_json = [ { 'text': question, 'fallback': 'Phantom cannot post questions on this channel.', 'callback_id': json.dumps(path_json), 'color': '#422E61', 'attachment_type': 'default', 'actions': answers } ] params = {'channel': user, 'attachments': json.dumps(answer_json), 'as_user': True} ret_val, resp_json = self._make_slack_rest_call(action_result, consts.SLACK_SEND_MESSAGE, params) if not ret_val: return phantom.APP_ERROR answer_path = "{0}/{1}".format(local_data_state_dir, answer_filename) loop_count = (self._timeout * 60) / self._interval count = 0 while True: if count >= loop_count: action_result.set_summary({'response_received': False, 'question_id': qid}) return action_result.set_status(phantom.APP_SUCCESS) try: answer_file = open(answer_path, 'r') except: count += 1 time.sleep(self._interval) continue resp_json = json.loads(answer_file.read()) break action_result.add_data(resp_json) action_result.set_summary({'response_received': True, 'question_id': qid, 'response': resp_json.get("actions", [{}])[0].get("value")}) os.remove(answer_path) return action_result.set_status(phantom.APP_SUCCESS)
def _stop_bot(self, param): action_result = self.add_action_result( phantom.ActionResult(dict(param))) pid = self._state.get('pid', '') if pid: self._state.pop('pid') try: if 'slack_bot.pyc' in sh.ps('ww', pid): # pylint: disable=E1101 sh.kill(pid) # pylint: disable=E1101 action_result.set_status(phantom.APP_SUCCESS, SLACK_SUCC_SLACKBOT_STOPPED) except: action_result.set_status(phantom.APP_SUCCESS, SLACK_SUCC_SLACKBOT_NOT_RUNNING) else: action_result.set_status(phantom.APP_SUCCESS, SLACK_SUCC_SLACKBOT_NOT_RUNNING) rest_url = SLACK_PHANTOM_ASSET_INFO_URL.format( url=self.get_phantom_base_url(), asset_id=self.get_asset_id()) ret_val, resp_json = self._make_rest_call(action_result, rest_url, False) if phantom.is_fail(ret_val): return ret_val asset_config = resp_json.get('configuration', {}) ingest_config = asset_config.get('ingest', {}) poll = ingest_config.get('poll') if poll is None: return action_result.set_status( phantom.APP_SUCCESS, SLACK_FAILED_TO_DISABLE_INGESTION.format( message=action_result.get_message())) if not poll: return action_result.set_status( phantom.APP_SUCCESS, SLACK_INGESTION_NOT_ENABLED.format( message=action_result.get_message())) ingest_config['poll'] = False body = {'configuration': asset_config} ret_val, resp_json = self._make_rest_call(action_result, rest_url, False, method=requests.post, body=body) if phantom.is_fail(ret_val): return ret_val return action_result.set_status( phantom.APP_SUCCESS, SLACK_INGESTION_DISABLED.format( message=action_result.get_message()))