diff --git a/Packs/MicrosoftExchangeOnPremise/Integrations/EWSv2/EWSv2.py b/Packs/MicrosoftExchangeOnPremise/Integrations/EWSv2/EWSv2.py index 56e50b524d6e..af1c3e1b5758 100644 --- a/Packs/MicrosoftExchangeOnPremise/Integrations/EWSv2/EWSv2.py +++ b/Packs/MicrosoftExchangeOnPremise/Integrations/EWSv2/EWSv2.py @@ -7,9 +7,9 @@ import dateparser # type: ignore import exchangelib from CommonServerPython import * -from cStringIO import StringIO +from io import StringIO from exchangelib import (BASIC, DELEGATE, DIGEST, IMPERSONATION, NTLM, Account, - Body, Build, Configuration, Credentials, EWSDateTime, + Build, Configuration, Credentials, EWSDateTime, EWSTimeZone, FileAttachment, Folder, HTMLBody, ItemAttachment, Version) from exchangelib.errors import (AutoDiscoverFailed, ErrorFolderNotFound, @@ -21,29 +21,44 @@ ErrorNameResolutionNoResults, RateLimitError, ResponseMessageError, TransportError) from exchangelib.items import Contact, Item, Message -from exchangelib.protocol import BaseProtocol, NoVerifyHTTPAdapter -from exchangelib.services import EWSAccountService, EWSService +from exchangelib.protocol import BaseProtocol, Protocol +from exchangelib.services import EWSService +from exchangelib.services.common import EWSAccountService from exchangelib.util import add_xml_child, create_element from exchangelib.version import (EXCHANGE_2007, EXCHANGE_2010, EXCHANGE_2010_SP2, EXCHANGE_2013, - EXCHANGE_2016) + EXCHANGE_2016, EXCHANGE_2019) from future import utils as future_utils from requests.exceptions import ConnectionError +from exchangelib.version import VERSIONS as EXC_VERSIONS + + +# Exchange2 2019 patch - server dosen't connect with 2019 but with other versions creating an error mismatch (see CIAC-3086), +# overriding this function to remove minor version test and remove error throw. +# opened bug for exchanglib here https://github.com/ecederstrand/exchangelib/issues/1210 +def our_fullname(self): # pragma: no cover + for build, api_version, full_name in EXC_VERSIONS: + if self.build: + if self.build.major_version != build.major_version: # removed 'or self.build.minor_version != build.minor_version' + continue + if self.api_version == api_version: + return full_name + + +Version.fullname = our_fullname + + +class exchangelibSSLAdapter(SSLAdapter): + def cert_verify(self, conn, url, verify, cert): + # We're overriding a method, so we have to keep the signature, although verify is unused + del verify + super().cert_verify(conn=conn, url=url, verify=False, cert=cert) -# Define utf8 as default encoding -reload(sys) -sys.setdefaultencoding('utf8') # pylint: disable=E1101 # Ignore warnings print to stdout warnings.filterwarnings("ignore") -# Docker BC -MNS = None -TNS = None -if exchangelib.__version__ == "1.12.0": - MNS, TNS = exchangelib.util.MNS, exchangelib.util.TNS -else: - MNS, TNS = exchangelib.transport.MNS, exchangelib.transport.TNS # pylint: disable=E1101 +MNS, TNS = exchangelib.util.MNS, exchangelib.util.TNS # consts VERSIONS = { @@ -51,7 +66,8 @@ '2010': EXCHANGE_2010, '2010_SP2': EXCHANGE_2010_SP2, '2013': EXCHANGE_2013, - '2016': EXCHANGE_2016 + '2016': EXCHANGE_2016, + '2019': EXCHANGE_2019 } ATTACHMENT_ID = "attachmentId" @@ -323,15 +339,11 @@ config = None credentials = None -PUBLIC_FOLDERS_ERROR = 'Please update your docker image to use public folders' -if IS_PUBLIC_FOLDER and exchangelib.__version__ != "1.12.0": - return_error(PUBLIC_FOLDERS_ERROR) - # NOTE: Same method used in EWSMailSender # If you are modifying this probably also need to modify in the other file def exchangelib_cleanup(): # pragma: no cover - key_protocols = exchangelib.protocol.CachingProtocol._protocol_cache.items() + key_protocols = list(exchangelib.protocol.CachingProtocol._protocol_cache.items()) try: exchangelib.close_connections() except Exception as ex: @@ -363,12 +375,13 @@ def get_auth_method(auth_method): # pragma: no cover def get_build(version_str): # pragma: no cover if version_str not in VERSIONS: - raise Exception("%s is unsupported version: %s. Choose one of" % (version_str, "\\".join(VERSIONS.keys()))) + raise Exception("%s is unsupported version: %s. Choose one of" % (version_str, "\\".join(list(VERSIONS.keys())))) return VERSIONS[version_str] def get_build_autodiscover(context_dict): # pragma: no cover build_params = context_dict["build"].split(".") + build_params = [int(i) for i in build_params] return Build(*build_params) @@ -378,7 +391,7 @@ def get_endpoint_autodiscover(context_dict): # pragma: no cover def get_version(version_str): if version_str not in VERSIONS: - raise Exception("%s is unsupported version: %s. Choose one of" % (version_str, "\\".join(VERSIONS.keys()))) + raise Exception("%s is unsupported version: %s. Choose one of" % (version_str, "\\".join(list(VERSIONS.keys())))) return Version(VERSIONS[version_str]) @@ -406,7 +419,7 @@ def prepare_context(credentials): # pragma: no cover except AutoDiscoverFailed: return_error("Auto discovery failed. Check credentials or configure manually") except Exception as e: - return_error(e.message) + return_error(str(e)) else: SERVER_BUILD = get_build_autodiscover(context_dict) EWS_SERVER = get_endpoint_autodiscover(context_dict) @@ -414,7 +427,7 @@ def prepare_context(credentials): # pragma: no cover def prepare(): # pragma: no cover if NON_SECURE: - BaseProtocol.HTTP_ADAPTER_CLS = NoVerifyHTTPAdapter + BaseProtocol.HTTP_ADAPTER_CLS = exchangelibSSLAdapter else: BaseProtocol.HTTP_ADAPTER_CLS = requests.adapters.HTTPAdapter @@ -570,20 +583,29 @@ def repr3(self): def str_to_unicode(obj): # pragma: no cover if isinstance(obj, dict): - obj = {k: str_to_unicode(v) for k, v in obj.iteritems()} + obj = {k: str_to_unicode(v) for k, v in list(obj.items())} elif isinstance(obj, list): - obj = map(str_to_unicode, obj) + obj = [str_to_unicode(k) for k in obj] elif isinstance(obj, str): - obj = unicode(obj, "utf-8") + obj = obj.encode("utf-8") return obj def filter_dict_null(d): # pragma: no cover if isinstance(d, dict): - return dict((k, v) for k, v in d.items() if v is not None) + return dict((k, v) for k, v in list(d.items()) if v is not None) return d +def is_empty_object(obj): + size = 0 + if isinstance(obj, map): + size = obj.__sizeof__() + else: + size = len(obj) + return size == 0 + + def get_attachment_name(attachment_name): # pragma: no cover if attachment_name is None or attachment_name == "": return 'demisto_untitled_attachment' @@ -591,11 +613,11 @@ def get_attachment_name(attachment_name): # pragma: no cover def get_entry_for_object(title, context_key, obj, headers=None): # pragma: no cover - if len(obj) == 0: + if is_empty_object(obj): return "There is no output results" obj = filter_dict_null(obj) if isinstance(obj, list): - obj = map(filter_dict_null, obj) + obj = [filter_dict_null(k) for k in obj] if headers and isinstance(obj, dict): headers = list(set(headers).intersection(set(obj.keys()))) @@ -614,14 +636,11 @@ def get_entry_for_object(title, context_key, obj, headers=None): # pragma: def get_items_from_mailbox(account, item_ids): # pragma: no cover if type(item_ids) is not list: item_ids = [item_ids] - items = map(lambda x: Item(item_id=x), item_ids) + items = [Item(id=x) for x in item_ids] result = list(account.fetch(ids=items)) result = [x for x in result if not (isinstance(x, ErrorItemNotFound) or isinstance(x, ErrorInvalidIdMalformed))] if len(result) != len(item_ids): raise Exception("One or more items were not found/malformed. Check the input item ids") - if exchangelib.__version__ != "1.12.0": # Docker BC - for item in result: - item.folder = Folder(account=account) return result @@ -633,8 +652,6 @@ def get_item_from_mailbox(account, item_id): # pragma: no cover def is_default_folder(folder_path, is_public): # pragma: no cover - if exchangelib.__version__ != "1.12.0": # Docker BC - return False if is_public is not None: return is_public @@ -654,7 +671,7 @@ def get_folder_by_path(account, path, is_public=False): # pragma: no cover if is_public: folder_result = account.public_folders_root - elif path == u'AllItems': + elif path == 'AllItems': folder_result = account.root else: folder_result = account.inbox.parent # Top of Information Store @@ -681,11 +698,11 @@ def call(self, item_id, move_item): # pragma: no cover def get_payload(self, item_id, move_item): # pragma: no cover junk = create_element('m:%s' % self.SERVICE_NAME, - IsJunk="true", - MoveItem="true" if move_item else "false") + {"IsJunk": "true", + "MoveItem": ("true" if move_item else "false")}) items_list = create_element('m:ItemIds') - item_element = create_element("t:ItemId", Id=item_id) + item_element = create_element("t:ItemId", {"Id": item_id}) items_list.append(item_element) junk.append(items_list) @@ -783,7 +800,7 @@ def call(self): if self.protocol.version.build < EXCHANGE_2013: raise NotImplementedError('%s is only supported for Exchange 2013 servers and later' % self.SERVICE_NAME) elements = self._get_elements(payload=self.get_payload()) - return map(lambda x: self.parse_element(x), elements) + return [self.parse_element(e) for e in elements] def get_payload(self): element = create_element( @@ -800,7 +817,8 @@ class SearchMailboxes(EWSService): def parse_element(element): # pragma: no cover to_recipients = element.find('{%s}ToRecipients' % TNS) if to_recipients: - to_recipients = map(lambda x: x.text if x is not None else None, to_recipients) + to_recipients = [x.text if x is not None else None for x in to_recipients] + result = { ITEM_ID: element.find('{%s}Id' % TNS).attrib['Id'] if element.find('{%s}Id' % TNS) is not None else None, MAILBOX: element.find('{%s}Mailbox/{%s}PrimarySmtpAddress' % (TNS, TNS)).text if element.find( @@ -823,7 +841,7 @@ def call(self, query, mailboxes): # pragma: no cover if self.protocol.version.build < EXCHANGE_2013: raise NotImplementedError('%s is only supported for Exchange 2013 servers and later' % self.SERVICE_NAME) elements = list(self._get_elements(payload=self.get_payload(query, mailboxes))) - return map(lambda x: self.parse_element(x), elements) + return [self.parse_element(x) for x in elements] def get_payload(self, query, mailboxes): # pragma: no cover def get_mailbox_search_scope(mailbox_id): @@ -867,7 +885,7 @@ def call(self, email_address, recursive_expansion=False): # pragma: no cover if recursive_expansion == 'True': group_members = {} # type: dict self.expand_group_recursive(email_address, group_members) - return group_members.values() + return list(group_members.values()) else: return self.expand_group(email_address) except ErrorNameResolutionNoResults: @@ -883,7 +901,7 @@ def get_payload(self, email_address): # pragma: no cover def expand_group(self, email_address): # pragma: no cover elements = self._get_elements(payload=self.get_payload(email_address)) - return map(lambda x: self.parse_element(x), elements) + return [self.parse_element(x) for x in elements] def expand_group_recursive(self, email_address, non_dl_emails, dl_emails=set()): # pragma: no cover if email_address in non_dl_emails or email_address in dl_emails: @@ -932,8 +950,8 @@ def search_mailboxes(protocol, filter, limit=100, mailbox_search_scope=None, ema mailbox_ids = mailbox_search_scope if type(mailbox_search_scope) is list else [mailbox_search_scope] else: entry = get_searchable_mailboxes(protocol) - mailboxes = [x for x in entry[ENTRY_CONTEXT]['EWS.Mailboxes'] if MAILBOX_ID in x.keys()] - mailbox_ids = map(lambda x: x[MAILBOX_ID], mailboxes) # type: ignore + mailboxes = [x for x in entry[ENTRY_CONTEXT]['EWS.Mailboxes'] if MAILBOX_ID in list(x.keys())] + mailbox_ids = [x[MAILBOX_ID] for x in mailboxes] # type: ignore try: search_results = SearchMailboxes(protocol=protocol).call(filter, mailbox_ids) @@ -974,11 +992,13 @@ def fetch_last_emails(account, folder_name='Inbox', since_datetime=None, exclude qs = qs.filter(datetime_received__gte=since_datetime) else: if not FETCH_ALL_HISTORY: - tz = EWSTimeZone.timezone('UTC') + tz = EWSTimeZone('UTC') first_fetch_datetime = dateparser.parse(FETCH_TIME) - first_fetch_ews_datetime = EWSDateTime.from_datetime(tz.localize(first_fetch_datetime)) + if not first_fetch_datetime: + raise DemistoException('Failed to parse first last run time') + first_fetch_ews_datetime = first_fetch_datetime.astimezone(tz) qs = qs.filter(datetime_received__gte=first_fetch_ews_datetime) - qs = qs.filter().only(*map(lambda x: x.name, Message.FIELDS)) + qs = qs.filter().only(*[x.name for x in Message.FIELDS]) qs = qs.filter().order_by('datetime_received') result = [] exclude_ids = exclude_ids if exclude_ids else set() @@ -997,23 +1017,27 @@ def fetch_last_emails(account, folder_name='Inbox', since_datetime=None, exclude future_utils.raise_from(ValueError( 'Got an error when pulling incidents. You might be using the wrong exchange version.' ), exc) + raise exc demisto.debug('EWS V2 - Got total of {} from ews query. '.format(len(result))) return result def keys_to_camel_case(value): def str_to_camel_case(snake_str): + # Add condtion as Email object arrived in list and raised error + if not isinstance(snake_str, str): + return snake_str components = snake_str.split('_') return components[0] + "".join(x.title() for x in components[1:]) if value is None: return None if isinstance(value, (list, set)): - return map(keys_to_camel_case, value) + return [keys_to_camel_case(v) for v in value] if isinstance(value, dict): return dict((keys_to_camel_case(k), keys_to_camel_case(v) if isinstance(v, (list, dict)) else v) - for (k, v) in value.items()) + for (k, v) in list(value.items())) return str_to_camel_case(value) @@ -1031,6 +1055,20 @@ def email_ec(item): } +def parse_object_as_dict_with_serialized_items(object): + raw_dict = {} + if object is not None: + for field in object.FIELDS: + try: + v = getattr(object, field.name, None) + json.dumps(v) + raw_dict[field.name] = v + except (TypeError, OverflowError): + demisto.debug(f'Data in field {field.name} is not serilizable, skipped field') + continue + return raw_dict + + def parse_item_as_dict(item, email_address, camel_case=False, compact_fields=False): # pragma: no cover def parse_object_as_dict(object): raw_dict = {} @@ -1055,18 +1093,10 @@ def parse_folder_as_json(folder): # pragma: no cover raw_dict['effective_rights'] = parse_object_as_dict(raw_dict['effective_rights']) return raw_dict - raw_dict = {} - for field, value in item.__dict__.items(): - if type(value) in [str, unicode, int, float, bool, Body, HTMLBody, None]: - try: - if isinstance(value, basestring): - value.encode('utf-8') # type: ignore - raw_dict[field] = value - except Exception: - pass + raw_dict = parse_object_as_dict_with_serialized_items(item) if getattr(item, 'attachments', None): - raw_dict['attachments'] = map(lambda x: parse_attachment_as_dict(item.item_id, x), item.attachments) + raw_dict['attachments'] = [parse_attachment_as_dict(item.id, x) for x in item.attachments] for time_field in ['datetime_sent', 'datetime_created', 'datetime_received', 'last_modified_time', 'reminder_due_by']: @@ -1083,7 +1113,7 @@ def parse_folder_as_json(folder): # pragma: no cover for list_dict_field in ['headers', 'cc_recipients', 'to_recipients']: value = getattr(item, list_dict_field, None) if value: - raw_dict[list_dict_field] = map(lambda x: parse_object_as_dict(x), value) + raw_dict[list_dict_field] = [parse_object_as_dict(x) for x in value] for list_str_field in ["categories"]: value = getattr(item, list_str_field, None) @@ -1096,18 +1126,15 @@ def parse_folder_as_json(folder): # pragma: no cover TOIS_PATH) else item.folder.absolute raw_dict['folder_path'] = folder_path + raw_dict['item_id'] = getattr(item, 'id', None) + if compact_fields: new_dict = {} fields_list = ['datetime_created', 'datetime_received', 'datetime_sent', 'sender', 'has_attachments', 'importance', 'message_id', 'last_modified_time', 'size', 'subject', 'text_body', 'headers', 'body', 'folder_path', 'is_read', 'categories'] - # Docker BC - if exchangelib.__version__ == "1.12.0": - if 'id' in raw_dict: - new_dict['item_id'] = raw_dict['id'] - else: - fields_list.append('item_id') + fields_list.append('item_id') for field in fields_list: if field in raw_dict: @@ -1117,7 +1144,7 @@ def parse_folder_as_json(folder): # pragma: no cover new_dict[field] = raw_dict.get(field, {}).get('email_address') for field in ['to_recipients']: if field in raw_dict: - new_dict[field] = map(lambda x: x.get('email_address'), raw_dict[field]) + new_dict[field] = [x.get('email_address') for x in raw_dict[field]] attachments = raw_dict.get('attachments') if attachments and len(attachments) > 0: file_attachments = [x for x in attachments if x[ATTACHMENT_TYPE] == FILE_ATTACHMENT_TYPE] @@ -1126,7 +1153,6 @@ def parse_folder_as_json(folder): # pragma: no cover item_attachments = [x for x in attachments if x[ATTACHMENT_TYPE] == ITEM_ATTACHMENT_TYPE] if len(item_attachments) > 0: new_dict['ItemAttachments'] = item_attachments - raw_dict = new_dict if camel_case: @@ -1208,7 +1234,7 @@ def parse_incident_from_item(item, is_fetch): # pragma: no cover 'name': get_attachment_name(attachment.name) }) except TypeError as e: - if e.message != "must be string or buffer, not None": + if str(e) != "must be string or buffer, not None": raise continue else: @@ -1218,10 +1244,14 @@ def parse_incident_from_item(item, is_fetch): # pragma: no cover # save the attachment if hasattr(attachment, 'item') and attachment.item.mime_content: - attached_email = email.message_from_string(attachment.item.mime_content) + # Some items arrive with bytes attachemnt + if isinstance(attachment.item.mime_content, bytes): + attached_email = email.message_from_bytes(attachment.item.mime_content) + else: + attached_email = email.message_from_string(attachment.item.mime_content) if attachment.item.headers: attached_email_headers = [] - for h, v in attached_email.items(): + for h, v in list(attached_email.items()): if not isinstance(v, str): try: v = str(v) @@ -1274,9 +1304,9 @@ def parse_incident_from_item(item, is_fetch): # pragma: no cover # fetch history incident['dbotMirrorId'] = str(item.message_id) - if item.item_id: - labels.append({'type': 'Email/ID', 'value': item.item_id}) - labels.append({'type': 'Email/itemId', 'value': item.item_id}) + if item.id: + labels.append({'type': 'Email/ID', 'value': item.id}) + labels.append({'type': 'Email/itemId', 'value': item.id}) # handle conversion id if item.conversation_id: @@ -1428,7 +1458,7 @@ def parse_attachment_as_dict(item_id, attachment): # pragma: no cover } except TypeError as e: - if e.message != "must be string or buffer, not None": + if str(e) != "must be string or buffer, not None": raise return { ATTACHMENT_ORIGINAL_ITEM_ID: item_id, @@ -1519,7 +1549,7 @@ def fetch_attachments_for_message(item_id, target_mailbox=None, attachment_ids=N if attachment.content: entries.append(get_entry_for_file_attachment(item_id, attachment)) except TypeError as e: - if e.message != "must be string or buffer, not None": + if str(e) != "must be string or buffer, not None": raise else: entries.append(get_entry_for_item_attachment(item_id, attachment, account.primary_smtp_address)) @@ -1565,7 +1595,7 @@ def move_item(item_id, target_folder_path, target_mailbox=None, is_public=None): raise Exception("Item not found") item.move(target_folder) move_result = { - NEW_ITEM_ID: item.item_id, + NEW_ITEM_ID: item.id, ITEM_ID: item_id, MESSAGE_ID: item.message_id, ACTION: 'moved' @@ -1585,7 +1615,7 @@ def delete_items(item_ids, delete_type, target_mailbox=None): # pragma: no c delete_type = delete_type.lower() for item in items: - item_id = item.item_id + item_id = item.id if delete_type == 'trash': item.move_to_trash() elif delete_type == 'soft': @@ -1606,12 +1636,9 @@ def delete_items(item_ids, delete_type, target_mailbox=None): # pragma: no c def prepare_args(d): # pragma: no cover - d = dict((k.replace("-", "_"), v) for k, v in d.items()) + d = dict((k.replace("-", "_"), v) for k, v in list(d.items())) if 'is_public' in d: - if exchangelib.__version__ != "1.12.0": # Docker BC - raise Exception(PUBLIC_FOLDERS_ERROR) - else: - d['is_public'] = d['is_public'] == 'True' + d['is_public'] = d['is_public'] == 'True' return d @@ -1649,7 +1676,7 @@ def search_items_in_mailbox(query=None, message_id=None, folder_path='', limit=1 selected_all_fields = (selected_fields == 'all') if selected_all_fields: - restricted_fields = list(map(lambda x: x.name, Message.FIELDS)) # type: ignore + restricted_fields = set([x.name for x in Message.FIELDS]) # type: ignore else: restricted_fields = set(argToList(selected_fields)) # type: ignore restricted_fields.update(['id', 'message_id']) # type: ignore @@ -1666,18 +1693,17 @@ def search_items_in_mailbox(query=None, message_id=None, folder_path='', limit=1 break items = items[:limit] - searched_items_result = map( - lambda item: parse_item_as_dict(item, account.primary_smtp_address, camel_case=True, - compact_fields=selected_all_fields), items) + searched_items_result = [parse_item_as_dict(item, account.primary_smtp_address, camel_case=True, + compact_fields=selected_all_fields) for item in items] if not selected_all_fields: + # we show id as 'itemId' for BC + restricted_fields.remove('id') + restricted_fields.add('itemId') searched_items_result = [ - {k: v for (k, v) in i.iteritems() + {k: v for (k, v) in i.items() if k in keys_to_camel_case(restricted_fields)} for i in searched_items_result] - for item in searched_items_result: - item['itemId'] = item.pop('id', '') - return get_entry_for_object('Searched items', CONTEXT_UPDATE_EWS_ITEM, searched_items_result, @@ -1710,12 +1736,12 @@ def recover_soft_delete_item(message_ids, target_folder_path="Inbox", target_mai message_ids = message_ids.split(",") items_to_recover = account.recoverable_items_deletions.filter( # pylint: disable=E1101 message_id__in=message_ids).all() # pylint: disable=E1101 - if len(items_to_recover) != len(message_ids): + if items_to_recover.count() != len(message_ids): raise Exception("Some message ids are missing in recoverable items directory") for item in items_to_recover: item.move(target_folder) recovered_messages.append({ - ITEM_ID: item.item_id, + ITEM_ID: item.id, MESSAGE_ID: item.message_id, ACTION: 'recovered' }) @@ -1737,19 +1763,29 @@ def parse_phone_number(phone_number): result[attr] = getattr(phone_number, attr, None) return result + def is_jsonable(x): + try: + json.dumps(x) + return True + except Exception: + return False + def parse_contact(contact): - contact_dict = dict((k, v if not isinstance(v, EWSDateTime) else v.ewsformat()) - for k, v in contact.__dict__.items() - if isinstance(v, basestring) or isinstance(v, EWSDateTime)) + contact_dict = parse_object_as_dict_with_serialized_items(contact) + for k in contact_dict.keys(): + v = contact_dict[k] + if isinstance(v, EWSDateTime): + contact_dict[k] = v.ewsformat() + + contact_dict['id'] = contact.id if isinstance(contact, Contact) and contact.physical_addresses: - contact_dict['physical_addresses'] = map(parse_physical_address, contact.physical_addresses) + contact_dict['physical_addresses'] = list(map(parse_physical_address, contact.physical_addresses)) if isinstance(contact, Contact) and contact.phone_numbers: - contact_dict['phone_numbers'] = map(parse_phone_number, contact.phone_numbers) + contact_dict['phone_numbers'] = list(map(parse_phone_number, contact.phone_numbers)) if isinstance(contact, Contact) and contact.email_addresses and len(contact.email_addresses) > 0: - contact_dict['emailAddresses'] = map(lambda x: x.email, contact.email_addresses) + contact_dict['emailAddresses'] = [x.email for x in contact.email_addresses] contact_dict = keys_to_camel_case(contact_dict) - contact_dict = dict((k, v) for k, v in contact_dict.items() if v) - del contact_dict['mimeContent'] + contact_dict = {k: v for k, v in contact_dict.items() if (v and is_jsonable(v))} contact_dict['originMailbox'] = target_mailbox return contact_dict @@ -1758,7 +1794,7 @@ def parse_contact(contact): for contact in account.contacts.all()[:int(limit)]: # pylint: disable=E1101 contacts.append(parse_contact(contact)) - return get_entry_for_object('Email contacts for %s' % target_mailbox or ACCOUNT_EMAIL, + return get_entry_for_object(f'Email contacts for {target_mailbox or ACCOUNT_EMAIL}', 'Account.Email(val.Address == obj.originMailbox).EwsContacts', contacts) @@ -1781,9 +1817,8 @@ def create_folder(new_folder_name, folder_path, target_mailbox=None): # prag def find_folders(target_mailbox=None, is_public=None): # pragma: no cover account = get_account(target_mailbox or ACCOUNT_EMAIL) root = account.root - if exchangelib.__version__ == "1.12.0": # Docker BC - if is_public: - root = account.public_folders_root + if is_public: + root = account.public_folders_root folders = [] for f in root.walk(): # pylint: disable=E1101 folder = folder_to_context_entry(f) @@ -1846,9 +1881,7 @@ def get_items_from_folder(folder_path, limit=100, target_mailbox=None, is_public items_result.append(item_attachment) hm_headers = ['sender', 'subject', 'hasAttachments', 'datetimeReceived', - 'receivedBy', 'author', 'toRecipients', ] - if exchangelib.__version__ == "1.12.0": # Docker BC - hm_headers.append('itemId') + 'receivedBy', 'author', 'toRecipients', 'itemId'] return get_entry_for_object('Items in folder ' + folder_path, CONTEXT_UPDATE_EWS_ITEM, items_result, @@ -1862,8 +1895,8 @@ def get_items(item_ids, target_mailbox=None): # pragma: no cover items = get_items_from_mailbox(account, item_ids) items = [x for x in items if isinstance(x, Message)] - items_as_incidents = map(lambda x: parse_incident_from_item(x, False), items) - items_to_context = map(lambda x: parse_item_as_dict(x, account.primary_smtp_address, True, True), items) + items_as_incidents = [parse_incident_from_item(x, False) for x in items] + items_to_context = [parse_item_as_dict(x, account.primary_smtp_address, True, True) for x in items] return { 'Type': entryTypes['note'], @@ -1889,12 +1922,12 @@ def folder_to_context_entry(f): f_entry = { 'name': f.name, 'totalCount': f.total_count, - 'id': f.folder_id, + 'id': f.id, 'childrenFolderCount': f.child_folder_count, 'changeKey': f.changekey } - if 'unread_count' in map(lambda x: x.name, Folder.FIELDS): + if 'unread_count' in [x.name for x in Folder.FIELDS]: f_entry['unreadCount'] = f.unread_count return f_entry @@ -1902,8 +1935,6 @@ def folder_to_context_entry(f): def check_cs_prereqs(): # pragma: no cover if 'outlook.office365.com' not in EWS_SERVER: raise Exception("This command is only supported for Office 365") - if exchangelib.__version__ != "1.12.0": - raise Exception("Please update your docker image to use this command") def get_cs_error(stderr): @@ -1932,9 +1963,9 @@ def start_compliance_search(query): # pragma: no cover f.write(START_COMPLIANCE) output = subprocess.Popen(["pwsh", "startcompliancesearch2.ps1", USERNAME, query], - stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='utf8') - stdout, stderr = output.communicate(input=PASSWORD.encode()) + stdout, stderr = output.communicate(input=PASSWORD) finally: os.remove("startcompliancesearch2.ps1") @@ -1951,7 +1982,7 @@ def start_compliance_search(query): # pragma: no cover return { 'Type': entryTypes['note'], 'ContentsFormat': formats['text'], - 'Contents': 'Search started: {}'.format(search_name), + 'Contents': f'Search started: {search_name!r}', 'EntryContext': { 'EWS.ComplianceSearch': {'Name': search_name, 'Status': 'Starting'} } @@ -1965,8 +1996,8 @@ def get_compliance_search(search_name, show_only_recipients): # pragma: no c f.write(GET_COMPLIANCE) output = subprocess.Popen(["pwsh", "getcompliancesearch2.ps1", USERNAME, search_name], - stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - stdout, stderr = output.communicate(input=PASSWORD.encode()) + stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='utf8') + stdout, stderr = output.communicate(input=PASSWORD) finally: os.remove("getcompliancesearch2.ps1") @@ -1984,7 +2015,7 @@ def get_compliance_search(search_name, show_only_recipients): # pragma: no c if stdout[0] == 'Completed': if stdout[1] and stdout[1] != '{}': res = list(r[:-1].split(', ') if r[-1] == ',' else r.split(', ') for r in stdout[1][2:-3].split(r'\r\n')) - res = map(lambda x: {k: v for k, v in (s.split(': ') for s in x)}, res) + res = [{k: v for k, v in (s.split(': ') for s in x)} for x in res] entry = { 'Type': entryTypes['note'], 'ContentsFormat': formats['text'], @@ -1992,7 +2023,7 @@ def get_compliance_search(search_name, show_only_recipients): # pragma: no c 'ReadableContentsFormat': formats['markdown'], } if show_only_recipients == 'True': - res = filter(lambda x: int(x['Item count']) > 0, res) + res = [x for x in res if int(x['Item count']) > 0] entry['EntryContext'] = { 'EWS.ComplianceSearch(val.Name == obj.Name)': { @@ -2023,8 +2054,8 @@ def purge_compliance_search(search_name): # pragma: no cover f.write(PURGE_COMPLIANCE) output = subprocess.Popen(["pwsh", "purgecompliancesearch2.ps1", USERNAME, search_name], - stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - _, stderr = output.communicate(input=PASSWORD.encode()) + stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='utf8') + _, stderr = output.communicate(input=PASSWORD) finally: os.remove("purgecompliancesearch2.ps1") @@ -2042,8 +2073,8 @@ def check_purge_compliance_search(search_name): # pragma: no cover f.write(PURGE_STATUS_COMPLIANCE) output = subprocess.Popen(["pwsh", "purgestatuscompliancesearch2.ps1", USERNAME, search_name], - stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - stdout, stderr = output.communicate(input=PASSWORD.encode()) + stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='utf8') + stdout, stderr = output.communicate(input=PASSWORD) stdout = stdout[len(PASSWORD):] @@ -2064,8 +2095,8 @@ def remove_compliance_search(search_name): # pragma: no cover output = subprocess.Popen( ["pwsh", "removecompliance2.ps1", USERNAME, search_name], - stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - stdout, stderr = output.communicate(input=PASSWORD.encode()) + stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='utf8') + stdout, stderr = output.communicate(input=PASSWORD) finally: os.remove("removecompliance2.ps1") @@ -2099,7 +2130,7 @@ def mark_item_as_read(item_ids, operation='read', target_mailbox=None): # p item.save() marked_items.append({ - ITEM_ID: item.item_id, + ITEM_ID: item.id, MESSAGE_ID: item.message_id, ACTION: 'marked-as-{}'.format(operation) }) @@ -2114,10 +2145,14 @@ def get_item_as_eml(item_id, target_mailbox=None): # pragma: no cover item = get_item_from_mailbox(account, item_id) if item.mime_content: - email_content = email.message_from_string(item.mime_content) + # came across an item with bytes attachemnt which failed in the source code, added this to keep functionality + if isinstance(item.mime_content, bytes): + email_content = email.message_from_bytes(item.mime_content) + else: + email_content = email.message_from_string(item.mime_content) if item.headers: attached_email_headers = [] - for h, v in email_content.items(): + for h, v in list(email_content.items()): if not isinstance(v, str): try: v = str(v) @@ -2283,7 +2318,7 @@ def test_module(): # pragma: no cover "please read integration documentation and follow the instructions") folder.test_access() except ErrorFolderNotFound as e: - if "Top of Information Store" in e.message: + if "Top of Information Store" in str(e): raise Exception( "Success to authenticate, but user probably has no permissions to read from the specific folder." "Check user permissions. You can try !ews-find-folders command to " @@ -2296,12 +2331,12 @@ def get_protocol(): # pragma: no cover if AUTO_DISCOVERY: protocol = get_account_autodiscover(ACCOUNT_EMAIL).protocol else: - protocol = config.protocol # type: ignore + protocol = Protocol(config=config) # type: ignore return protocol def encode_and_submit_results(obj): - demisto.results(str_to_unicode(obj)) + demisto.results(obj) def sub_main(): # pragma: no cover @@ -2321,7 +2356,7 @@ def sub_main(): # pragma: no cover test_module() elif demisto.command() == 'fetch-incidents': incidents = fetch_emails_as_incidents(ACCOUNT_EMAIL, FOLDER_NAME) - demisto.incidents(str_to_unicode(incidents) or []) + demisto.incidents(incidents) elif demisto.command() == 'ews-get-attachment': encode_and_submit_results(fetch_attachments_for_message(**args)) elif demisto.command() == 'ews-delete-attachment': @@ -2402,17 +2437,13 @@ def sub_main(): # pragma: no cover sys.exit(0) error_message_simple = log_message + " Please retry your request." - # Other exception handling - if isinstance(e.message, Exception): - e.message = str(e.message) - if isinstance(e, ConnectionError): error_message_simple = "Could not connect to the server.\n" \ "Verify that the Hostname or IP address is correct.\n\n" \ - "Additional information: {}".format(e.message) + "Additional information: {}".format(str(e)) if isinstance(e, ErrorInvalidPropertyRequest): error_message_simple = "Verify that the Exchange version is correct." - elif exchangelib.__version__ == "1.12.0": + else: from exchangelib.errors import MalformedResponseError if IS_TEST_MODULE and isinstance(e, MalformedResponseError): @@ -2440,9 +2471,9 @@ def sub_main(): # pragma: no cover "Check proxy parameter. If you are using server URL - change to server IP address. " if not error_message_simple: - error_message = error_message_simple = str(e.message) + error_message = error_message_simple = str(e) else: - error_message = error_message_simple + "\n" + str(e.message) + error_message = error_message_simple + "\n" + str(e) stacktrace = traceback.format_exc() if stacktrace: diff --git a/Packs/MicrosoftExchangeOnPremise/Integrations/EWSv2/EWSv2.yml b/Packs/MicrosoftExchangeOnPremise/Integrations/EWSv2/EWSv2.yml index e7a6d148baba..8c37bce58117 100644 --- a/Packs/MicrosoftExchangeOnPremise/Integrations/EWSv2/EWSv2.yml +++ b/Packs/MicrosoftExchangeOnPremise/Integrations/EWSv2/EWSv2.yml @@ -1016,8 +1016,8 @@ script: name: query required: true secret: false - deprecated: false - description: Starts a compliance search. + deprecated: true + description: This command is deprecated. Use the o365-sc-start-search command from Security And Compliance V2 instead. Starts a compliance search. execution: false name: ews-o365-start-compliance-search outputs: @@ -1045,8 +1045,8 @@ script: - 'False' required: false secret: false - deprecated: false - description: Returns the status and results of a compliance search. + deprecated: true + description: This command is deprecated. Use the o365-sc-get-search command Security And Compliance V2 instead. Returns the status and results of a compliance search. execution: false name: ews-o365-get-compliance-search outputs: @@ -1069,8 +1069,8 @@ script: name: search-name required: true secret: false - deprecated: false - description: Purges the results found in the compliance search. + deprecated: true + description: This command is deprecated. Use the o365-sc-new-search-action command from Security And Compliance V2. Purges the results found in the compliance search. execution: false name: ews-o365-purge-compliance-search-results outputs: @@ -1084,8 +1084,8 @@ script: name: search-name required: true secret: false - deprecated: false - description: Removes the compliance search. + deprecated: true + description: This command is deprecated. Use the o365-sc-remove-search command from Security And Compliance V2. Removes the compliance search. execution: false name: ews-o365-remove-compliance-search outputs: @@ -1099,8 +1099,8 @@ script: name: search-name required: true secret: false - deprecated: false - description: Checks the status of the purge operation on the compliance search. + deprecated: true + description: This command is deprecated. Use the o365-sc-get-search-action command from Security And Compliance V2 instead .Checks the status of the purge operation on the compliance search. execution: false name: ews-o365-get-compliance-search-purge-status outputs: @@ -1301,19 +1301,20 @@ script: description: Replies to an email using EWS. execution: false name: reply-mail - dockerimage: demisto/py-ews:1.0.0.49868 + dockerimage: demisto/py-ews:5.0.2.63879 feed: false isfetch: true longRunning: false longRunningPort: false runonce: false script: '-' - subtype: python2 + subtype: python3 type: python tests: - pyEWS_Test - EWS V2 Send Mail Test - EWS V2 Send Mail Test 2 +- EWS Public Folders Test defaultmapperin: EWS v2-mapper defaultclassifier: EWS v2 fromversion: 5.0.0 diff --git a/Packs/MicrosoftExchangeOnPremise/Integrations/EWSv2/EWSv2_test.py b/Packs/MicrosoftExchangeOnPremise/Integrations/EWSv2/EWSv2_test.py index 15eda6f1c54f..74d37a9d5ca9 100644 --- a/Packs/MicrosoftExchangeOnPremise/Integrations/EWSv2/EWSv2_test.py +++ b/Packs/MicrosoftExchangeOnPremise/Integrations/EWSv2/EWSv2_test.py @@ -107,7 +107,8 @@ def order_by(self, *args): return [Message(), Message(), Message(), Message(), Message()] client = TestNormalCommands.MockClient() - mocker.patch.object(dateparser, 'parse', return_value=datetime.datetime(2021, 5, 23, 13, 18, 14, 901293)) + mocker.patch.object(dateparser, 'parse', return_value=datetime.datetime(2021, 5, 23, 13, 18, 14, 901293, + datetime.timezone.utc)) mocker.patch.object(EWSv2, 'get_folder_by_path', return_value=MockObject()) mocker.patch.object(MockObject, 'filter') @@ -206,33 +207,33 @@ def test_dateparser(): message_id='message1', text_body='Hello World', body='message1', - datetime_received=EWSDateTime(2021, 7, 14, 13, 00, 00, tzinfo=EWSTimeZone.timezone('UTC')), - datetime_sent=EWSDateTime(2021, 7, 14, 13, 00, 00, tzinfo=EWSTimeZone.timezone('UTC')), - datetime_created=EWSDateTime(2021, 7, 14, 13, 00, 00, tzinfo=EWSTimeZone.timezone('UTC')) + datetime_received=EWSDateTime(2021, 7, 14, 13, 00, 00, tzinfo=EWSTimeZone('UTC')), + datetime_sent=EWSDateTime(2021, 7, 14, 13, 00, 00, tzinfo=EWSTimeZone('UTC')), + datetime_created=EWSDateTime(2021, 7, 14, 13, 00, 00, tzinfo=EWSTimeZone('UTC')) ), Message(subject='message2', message_id='message2', text_body='Hello World', body='message2', - datetime_received=EWSDateTime(2021, 7, 14, 13, 9, 00, tzinfo=EWSTimeZone.timezone('UTC')), - datetime_sent=EWSDateTime(2021, 7, 14, 13, 9, 00, tzinfo=EWSTimeZone.timezone('UTC')), - datetime_created=EWSDateTime(2021, 7, 14, 13, 9, 00, tzinfo=EWSTimeZone.timezone('UTC')) + datetime_received=EWSDateTime(2021, 7, 14, 13, 9, 00, tzinfo=EWSTimeZone('UTC')), + datetime_sent=EWSDateTime(2021, 7, 14, 13, 9, 00, tzinfo=EWSTimeZone('UTC')), + datetime_created=EWSDateTime(2021, 7, 14, 13, 9, 00, tzinfo=EWSTimeZone('UTC')) ), Message(subject='message3', message_id='message3', text_body='Hello World', body='message3', - datetime_received=EWSDateTime(2021, 7, 14, 13, 9, 00, tzinfo=EWSTimeZone.timezone('UTC')), - datetime_sent=EWSDateTime(2021, 7, 14, 13, 9, 00, tzinfo=EWSTimeZone.timezone('UTC')), - datetime_created=EWSDateTime(2021, 7, 14, 13, 9, 00, tzinfo=EWSTimeZone.timezone('UTC')) + datetime_received=EWSDateTime(2021, 7, 14, 13, 9, 00, tzinfo=EWSTimeZone('UTC')), + datetime_sent=EWSDateTime(2021, 7, 14, 13, 9, 00, tzinfo=EWSTimeZone('UTC')), + datetime_created=EWSDateTime(2021, 7, 14, 13, 9, 00, tzinfo=EWSTimeZone('UTC')) ), Message(subject='message4', message_id='message4', text_body='Hello World', body='message4', - datetime_received=EWSDateTime(2021, 7, 14, 13, 10, 00, tzinfo=EWSTimeZone.timezone('UTC')), - datetime_sent=EWSDateTime(2021, 7, 14, 13, 9, 00, tzinfo=EWSTimeZone.timezone('UTC')), - datetime_created=EWSDateTime(2021, 7, 14, 13, 11, 00, tzinfo=EWSTimeZone.timezone('UTC')) + datetime_received=EWSDateTime(2021, 7, 14, 13, 10, 00, tzinfo=EWSTimeZone('UTC')), + datetime_sent=EWSDateTime(2021, 7, 14, 13, 9, 00, tzinfo=EWSTimeZone('UTC')), + datetime_created=EWSDateTime(2021, 7, 14, 13, 11, 00, tzinfo=EWSTimeZone('UTC')) ), ] CASE_FIRST_RUN_NO_INCIDENT = ( @@ -453,11 +454,23 @@ def test_categories_parse_item_as_dict(): message_id='message4', text_body='Hello World', body='message4', - datetime_received=EWSDateTime(2021, 7, 14, 13, 10, 00, tzinfo=EWSTimeZone.timezone('UTC')), - datetime_sent=EWSDateTime(2021, 7, 14, 13, 9, 00, tzinfo=EWSTimeZone.timezone('UTC')), - datetime_created=EWSDateTime(2021, 7, 14, 13, 11, 00, tzinfo=EWSTimeZone.timezone('UTC')), + datetime_received=EWSDateTime(2021, 7, 14, 13, 10, 00, tzinfo=EWSTimeZone('UTC')), + datetime_sent=EWSDateTime(2021, 7, 14, 13, 9, 00, tzinfo=EWSTimeZone('UTC')), + datetime_created=EWSDateTime(2021, 7, 14, 13, 11, 00, tzinfo=EWSTimeZone('UTC')), categories=['Purple category', 'Orange category'] ) return_value = parse_item_as_dict(message, False) assert return_value.get("categories") == ['Purple category', 'Orange category'] + + +def test_get_entry_for_object_empty(): + from EWSv2 import get_entry_for_object + obj = {} + assert get_entry_for_object("test", "keyTest", obj) == "There is no output results" + + +def test_get_entry_for_object(): + from EWSv2 import get_entry_for_object + obj = {"a": 1, "b": 2} + assert get_entry_for_object("test", "keyTest", obj)['HumanReadable'] == '### test\n|a|b|\n|---|---|\n| 1 | 2 |\n' diff --git a/Packs/MicrosoftExchangeOnPremise/ReleaseNotes/2_0_0.md b/Packs/MicrosoftExchangeOnPremise/ReleaseNotes/2_0_0.md new file mode 100644 index 000000000000..ac62b360068d --- /dev/null +++ b/Packs/MicrosoftExchangeOnPremise/ReleaseNotes/2_0_0.md @@ -0,0 +1,12 @@ + +#### Integrations + +##### EWS v2 +- Updated the Docker image to: *demisto/py-ews:5.0.2.63879*. +Updated integration to support Exchange 2019, and use python3. +- Command ***ews-o365-purge-compliance-search-results*** is deprecated. Use o365-sc-new-search-action command from Security And Compliance V2 instead. +- Command ***ews-o365-get-compliance-search-purge-status*** is deprecated. Use o365-sc-get-search-action command from Security And Compliance V2 instead. +- Command ***ews-o365-remove-compliance-search*** is deprecated. Use ews-o365-remove-compliance-search instead. +- Command ***ews-o365-get-compliance-search*** is deprecated. Use o365-sc-get-search command Security And Compliance V2 instead. +- Command ***ews-o365-start-compliance-search*** is deprecated. Use o365-sc-start-search command from Security And Compliance V2 instead. + diff --git a/Packs/MicrosoftExchangeOnPremise/TestPlaybooks/playbook-EWS-V2-send-mail-Test.yml b/Packs/MicrosoftExchangeOnPremise/TestPlaybooks/playbook-EWS-V2-send-mail-Test.yml index 6bbe977070e9..d0618805a798 100644 --- a/Packs/MicrosoftExchangeOnPremise/TestPlaybooks/playbook-EWS-V2-send-mail-Test.yml +++ b/Packs/MicrosoftExchangeOnPremise/TestPlaybooks/playbook-EWS-V2-send-mail-Test.yml @@ -5,18 +5,20 @@ starttaskid: "0" tasks: "0": id: "0" - taskid: 21aaed67-8b83-4255-8f91-5686ea984c61 + taskid: b6a30efd-f6f6-451b-8c46-92d8921fd224 type: start task: - id: 21aaed67-8b83-4255-8f91-5686ea984c61 + id: b6a30efd-f6f6-451b-8c46-92d8921fd224 version: -1 name: "" iscommand: false brand: "" + description: '' nexttasks: '#none#': - "8" separatecontext: false + continueonerrortype: "" view: |- { "position": { @@ -33,10 +35,10 @@ tasks: isautoswitchedtoquietmode: false "1": id: "1" - taskid: 5c5c9851-198d-4275-8860-8886f4dc899a + taskid: c556fd47-93f7-49d6-8f98-82c96426c0be type: regular task: - id: 5c5c9851-198d-4275-8860-8886f4dc899a + id: c556fd47-93f7-49d6-8f98-82c96426c0be version: -1 name: Test send mail script: EWS v2|||send-mail @@ -49,17 +51,12 @@ tasks: scriptarguments: attachIDs: simple: ${File.EntryID} - attachNames: {} - bcc: {} - body: {} - cc: {} - htmlBody: {} - replyTo: {} subject: simple: test to: simple: buildtests@demisto.int separatecontext: false + continueonerrortype: "" view: |- { "position": { @@ -76,10 +73,10 @@ tasks: isautoswitchedtoquietmode: false "2": id: "2" - taskid: 371db546-d62c-437e-8ae5-b5d62b48cde1 + taskid: 25e6d467-8660-4058-8ad3-f8c47a4bb5e2 type: regular task: - id: 371db546-d62c-437e-8ae5-b5d62b48cde1 + id: 25e6d467-8660-4058-8ad3-f8c47a4bb5e2 version: -1 name: Get file scriptName: http @@ -90,22 +87,16 @@ tasks: '#none#': - "1" scriptarguments: - body: {} filename: simple: test_file - headers: {} - insecure: {} method: simple: GET - password: {} - proxy: {} saveAsFile: simple: "yes" - unsecure: {} url: simple: https://raw.githubusercontent.com/demisto/content/master/TestData/ParseEmailFiles_test_email.eml - username: {} separatecontext: false + continueonerrortype: "" view: |- { "position": { @@ -122,19 +113,21 @@ tasks: isautoswitchedtoquietmode: false "3": id: "3" - taskid: 81e8e117-3c88-42c4-8bab-e87a8e9fbe91 + taskid: 1500bd1b-66d7-4586-85c4-089dcf371cc9 type: title task: - id: 81e8e117-3c88-42c4-8bab-e87a8e9fbe91 + id: 1500bd1b-66d7-4586-85c4-089dcf371cc9 version: -1 name: Test success after failure type: title iscommand: false brand: "" + description: '' nexttasks: '#none#': - "4" separatecontext: false + continueonerrortype: "" view: |- { "position": { @@ -151,10 +144,10 @@ tasks: isautoswitchedtoquietmode: false "4": id: "4" - taskid: 0c7e82c3-ac71-42ae-801d-9d492d673f7c + taskid: 0cd75765-297c-4ea1-872c-17d1c5436cd9 type: regular task: - id: 0c7e82c3-ac71-42ae-801d-9d492d673f7c + id: 0cd75765-297c-4ea1-872c-17d1c5436cd9 version: -1 name: Send Bad Email (Should fail) description: Sends an email using EWS. @@ -166,21 +159,15 @@ tasks: '#none#': - "5" scriptarguments: - attachCIDs: {} attachIDs: simple: bad - attachNames: {} - bcc: {} - body: {} - cc: {} - htmlBody: {} - replyTo: {} subject: simple: Bad Email That Fails to: simple: buildtests@demisto.int - continueonerror: true separatecontext: false + continueonerror: true + continueonerrortype: "" view: |- { "position": { @@ -197,10 +184,10 @@ tasks: isautoswitchedtoquietmode: false "5": id: "5" - taskid: 1ce3f82a-9aea-4edc-8891-ee721c063342 + taskid: 98514559-3405-440a-81e5-886dd9a653bd type: regular task: - id: 1ce3f82a-9aea-4edc-8891-ee721c063342 + id: 98514559-3405-440a-81e5-886dd9a653bd version: -1 name: Send Good Email script: EWS v2|||send-mail @@ -208,20 +195,14 @@ tasks: iscommand: true brand: EWS v2 scriptarguments: - attachCIDs: {} attachIDs: simple: ${File.EntryID} - attachNames: {} - bcc: {} - body: {} - cc: {} - htmlBody: {} - replyTo: {} subject: simple: test to: simple: buildtests@demisto.int separatecontext: false + continueonerrortype: "" view: |- { "position": { @@ -238,10 +219,10 @@ tasks: isautoswitchedtoquietmode: false "8": id: "8" - taskid: 3e9a3f05-6619-4700-86e7-57ca4e3ae7e3 + taskid: 42c66f7d-f513-4a63-8b77-073fe64f11b0 type: regular task: - id: 3e9a3f05-6619-4700-86e7-57ca4e3ae7e3 + id: 42c66f7d-f513-4a63-8b77-073fe64f11b0 version: -1 name: DeleteContext description: Delete field from context @@ -255,11 +236,8 @@ tasks: scriptarguments: all: simple: "yes" - index: {} - key: {} - keysToKeep: {} - subplaybook: {} separatecontext: false + continueonerrortype: "" view: |- { "position": { diff --git a/Packs/MicrosoftExchangeOnPremise/TestPlaybooks/playbook-Get_Original_Email_-_EWS_v2_-_test.yml b/Packs/MicrosoftExchangeOnPremise/TestPlaybooks/playbook-Get_Original_Email_-_EWS_v2_-_test.yml index 917b19714df6..3299ad466b60 100644 --- a/Packs/MicrosoftExchangeOnPremise/TestPlaybooks/playbook-Get_Original_Email_-_EWS_v2_-_test.yml +++ b/Packs/MicrosoftExchangeOnPremise/TestPlaybooks/playbook-Get_Original_Email_-_EWS_v2_-_test.yml @@ -6,10 +6,10 @@ starttaskid: "0" tasks: "0": id: "0" - taskid: 15d27661-5221-4106-81cb-1beb7ce1e7fd + taskid: 03873e01-5b6e-43e1-82ac-28d95a1288b0 type: start task: - id: 15d27661-5221-4106-81cb-1beb7ce1e7fd + id: 03873e01-5b6e-43e1-82ac-28d95a1288b0 version: -1 name: "" iscommand: false @@ -19,6 +19,7 @@ tasks: '#none#': - "5" separatecontext: false + continueonerrortype: "" view: |- { "position": { @@ -35,10 +36,10 @@ tasks: isautoswitchedtoquietmode: false "2": id: "2" - taskid: f00380e0-6efc-4c9c-87ff-279b353d80a9 + taskid: 1548afd0-21ca-4c8e-85d1-f2efa6e41c3a type: condition task: - id: f00380e0-6efc-4c9c-87ff-279b353d80a9 + id: 1548afd0-21ca-4c8e-85d1-f2efa6e41c3a version: -1 name: Check output description: Check the playbook outputs. @@ -54,16 +55,14 @@ tasks: conditions: - label: "yes" condition: - - - operator: isEqualString + - - operator: isNotEmpty left: value: complex: root: File accessor: SHA256 iscontext: true - right: - value: - simple: b566b3812fcfaeb3474f9b43dc891a51e9a378d00d57d4e3048017715ef18fe1 + continueonerrortype: "" view: |- { "position": { @@ -80,10 +79,10 @@ tasks: isautoswitchedtoquietmode: false "3": id: "3" - taskid: 23abaa87-7be6-4efa-84c3-d7e0689f3d27 + taskid: 8b153235-d4eb-4f8c-898d-8cf985fe3dca type: regular task: - id: 23abaa87-7be6-4efa-84c3-d7e0689f3d27 + id: 8b153235-d4eb-4f8c-898d-8cf985fe3dca version: -1 name: Print success description: Prints text to war room (Markdown supported) @@ -95,6 +94,7 @@ tasks: value: simple: SUCCESS separatecontext: false + continueonerrortype: "" view: |- { "position": { @@ -111,10 +111,10 @@ tasks: isautoswitchedtoquietmode: false "4": id: "4" - taskid: acf8565e-b649-41af-8631-75ddd75af037 + taskid: 2634bc10-1180-4987-872e-80b1506eae70 type: regular task: - id: acf8565e-b649-41af-8631-75ddd75af037 + id: 2634bc10-1180-4987-872e-80b1506eae70 version: -1 name: Print Error description: Prints an error entry with a given message @@ -126,6 +126,7 @@ tasks: message: simple: ERROR separatecontext: false + continueonerrortype: "" view: |- { "position": { @@ -142,10 +143,10 @@ tasks: isautoswitchedtoquietmode: false "5": id: "5" - taskid: eee8500a-b9c6-4e6e-8565-6b15a55d1ef2 + taskid: c82679a1-c7e3-4716-8444-e12f787602d6 type: playbook task: - id: eee8500a-b9c6-4e6e-8565-6b15a55d1ef2 + id: c82679a1-c7e3-4716-8444-e12f787602d6 version: -1 name: Get Original Email - EWS v2 description: |- @@ -165,6 +166,7 @@ tasks: TargetMailbox: simple: ${inputs.UserID} separatecontext: true + continueonerrortype: "" loop: iscommand: false exitCondition: "" diff --git a/Packs/MicrosoftExchangeOnPremise/pack_metadata.json b/Packs/MicrosoftExchangeOnPremise/pack_metadata.json index 4da4ba220616..78f2bc5a198f 100644 --- a/Packs/MicrosoftExchangeOnPremise/pack_metadata.json +++ b/Packs/MicrosoftExchangeOnPremise/pack_metadata.json @@ -2,7 +2,7 @@ "name": "Microsoft Exchange On-Premise", "description": "Exchange Web Services", "support": "xsoar", - "currentVersion": "1.0.12", + "currentVersion": "2.0.0", "author": "Cortex XSOAR", "url": "https://www.paloaltonetworks.com/cortex", "email": "", diff --git a/Tests/conf.json b/Tests/conf.json index dce715b95537..2b361899a273 100644 --- a/Tests/conf.json +++ b/Tests/conf.json @@ -3148,6 +3148,7 @@ "playbookID": "Get EWS Folder Test", "fromversion": "4.5.0", "instance_names": "ewv2_regular", + "memory_threshold": 100, "timeout": 1200 }, { @@ -4809,6 +4810,7 @@ { "playbookID": "Send Email To Recipients", "fromversion": "5.0.0", + "memory_threshold": 100, "integrations": [ "EWS v2" ], @@ -5112,6 +5114,7 @@ { "playbookID": "Get Original Email - EWS v2 - test", "fromversion": "6.1.0", + "memory_threshold": 100, "integrations": [ "EWS v2" ],