MicrosoftGraphTeamsApiModule
Provides common Microsoft Graph Teams code that is appended into Cortex integrations during deployment.
python · ApiModules
Details
| ID | MicrosoftGraphTeamsApiModule |
|---|---|
| Language | python |
| From Version | 6.10.0 |
| Docker Image | demisto/crypto:1.0.0.11658507 |
| Tags | infra server |
README
To use the common Microsoft Graph Teams integration logic, add the following code to import the MicrosoftGraphTeamsApiModule.
def main():
run_microsoft_graph_teams_integration()
from MicrosoftGraphTeamsApiModule import * # noqa: E402
if __name__ in ["builtins", "__main__"]:
main()
Then, the run_microsoft_graph_teams_integration entry point and MsGraphClient class will be available for usage. For the canonical consumer, see the O365 Teams (Using Graph API) integration in the MicrosoftGraphTeams pack.
import demistomock as demisto # noqa: F401 from CommonServerPython import * # noqa: F401 """ IMPORTS """ import urllib3 from MicrosoftApiModule import * # noqa: E402 # Disable insecure warnings urllib3.disable_warnings() """ GLOBAL VARS """ """ CLIENT """ class MsGraphClient: def __init__( self, tenant_id, auth_id, enc_key, app_name, base_url, verify, proxy, self_deployed, redirect_uri, auth_code, handle_error, certificate_thumbprint: str | None = None, private_key: str | None = None, delegated_user: str | None = None, ): grant_type = AUTHORIZATION_CODE if auth_code and redirect_uri else CLIENT_CREDENTIALS resource = None if self_deployed else "" self.ms_client = MicrosoftClient( tenant_id=tenant_id, auth_id=auth_id, enc_key=enc_key, app_name=app_name, base_url=base_url, verify=verify, proxy=proxy, self_deployed=self_deployed, redirect_uri=redirect_uri, auth_code=auth_code, grant_type=grant_type, resource=resource, certificate_thumbprint=certificate_thumbprint, private_key=private_key, command_prefix="msgraph-teams", ) self.handle_error = handle_error self.delegated_user = delegated_user @staticmethod def _build_members_input(user_id, members): """ Builds valid members input for the chat. :type members: ``list`` :param members: List of principle names (email) :return: List of valid member objects :rtype: ``list`` """ member_list = [] member_list.append( { "@odata.type": "#microsoft.graph.aadUserConversationMember", "roles": ["owner"], "user@odata.bind": f"https://graph.microsoft.com/v1.0/users('{user_id}')", } ) for member in members: member_list.append( { "@odata.type": "#microsoft.graph.aadUserConversationMember", "roles": ["owner"], "user@odata.bind": f"https://graph.microsoft.com/v1.0/users('{member}')", } ) return member_list @staticmethod def _get_user_name(user: dict): """ Receives dict of form "emailAddress":{"name":"_", "address":"_"} and return the address :type user: ``dict`` :param user: user :return: The name of the user :rtype: ``str`` """ return user.get("user", {}).get("displayName", "") @staticmethod def _get_body_content(body: dict): """ Receives dict of form "emailAddress":{"name":"_", "address":"_"} and return the address :type body: ``dict`` :param body: body :return: The content of the body :rtype: ``str`` """ return body.get("content", "") def pages_puller(self, response: dict, page_count: int = 100) -> list: """Gets first response from API and returns all pages Args: response (dict): page_count (int): Returns: list: list of all pages """ responses = [response] for _i in range(page_count - 1): next_link = response.get("@odata.nextLink") if next_link: response = self.ms_client.http_request("GET", full_url=next_link, url_suffix=None) responses.append(response) else: return responses return responses def list_chats(self, user_id: str = None, odata: str = None, limit: str = "20") -> dict | list: """Returning all chats from given user Args: user_id (str): odata (str): limit (str): Returns: dict or list: """ user_id = user_id if user_id else self.delegated_user suffix = f"users/{user_id}/chats" odata = f"{odata}&$top={limit}" if odata else f"$top={limit}" if odata: suffix += f"?{odata}" demisto.debug(f"URL suffix is {suffix}") response = self.ms_client.http_request(method="GET", url_suffix=suffix) return self.pages_puller(response, 1) def create_chat(self, subject: str, members: list, user_id: str = None, type="group") -> dict: """Create a new chat for a given user Args: subject (str): members (list): user_id (str): type (str): Returns: dict: """ user_id = user_id if user_id else self.delegated_user suffix = "chats" if type == "group": json_data = {"chatType": type, "topic": subject, "members": self._build_members_input(user_id, members)} else: json_data = {"chatType": type, "members": self._build_members_input(user_id, members)} return self.ms_client.http_request(method="POST", url_suffix=suffix, json_data=json_data) def get_chat(self, chat_id: str, user_id: str = None) -> dict: """retrieves an existing chat for a given user Args: chat_id (str): user_id (str): Returns: dict: """ user_id = user_id if user_id else self.delegated_user suffix = f"users/{user_id}/chats/{chat_id}" demisto.debug(f"URL suffix is {suffix}") response = self.ms_client.http_request(method="GET", url_suffix=suffix) return response def update_chat(self, chat_id: str, subject: str) -> dict: """Updates an existing chat for a given user Args: chat_id (str): subject (str): Returns: dict: """ suffix = f"chats/{chat_id}" json_data = {"topic": subject} return self.ms_client.http_request(method="PATCH", url_suffix=suffix, json_data=json_data) def list_members(self, chat_id: str, user_id: str = None) -> dict | list: """Returning all members from given chat Args: chat_id (str): user_id (str): Returns: dict or list: """ user_id = user_id if user_id else self.delegated_user suffix = f"users/{user_id}/chats/{chat_id}/members" demisto.debug(f"URL suffix is {suffix}") response = self.ms_client.http_request(method="GET", url_suffix=suffix) return response def add_member(self, chat_id: str, user_id: str, share_history: bool) -> bool: """Add a member to a given chat Args: chat_id (str): user_id (str): share_history (bool): Returns: bool: """ suffix = f"chats/{chat_id}/members" json_data = { "@odata.type": "#microsoft.graph.aadUserConversationMember", "roles": ["owner"], "user@odata.bind": f"https://graph.microsoft.com/v1.0/users('{user_id}')", "visibleHistoryStartDateTime": "0001-01-01T00:00:00Z" if share_history else "", } self.ms_client.http_request(method="POST", url_suffix=suffix, json_data=json_data, resp_type="text") return True def list_messages(self, chat_id: str, user_id: str = None, limit: str = "50") -> dict | list: """Returning all mails from given user Args: chat_id (str): user_id (str): limit (str): Returns: dict or list: """ user_id = user_id if user_id else self.delegated_user suffix = f"chats/{chat_id}/messages" suffix += f"?$top={limit}" demisto.debug(f"URL suffix is {suffix}") response = self.ms_client.http_request(method="GET", url_suffix=suffix) return self.pages_puller(response) def send_message(self, chat_id: str, body: str) -> dict: """Returning all mails from given user Args: chat_id (str): body (str): Returns: dict: """ suffix = f"chats/{chat_id}/messages" json_data = {"body": {"contentType": "html", "content": body}} return self.ms_client.http_request(method="POST", url_suffix=suffix, json_data=json_data) def update_message_policy_violation(self, url_suffix: str, body: dict) -> requests.Response: """Apply a data loss prevention policy violation to a Teams message. Args: url_suffix: The message resource path (chat, channel, or channel reply). body: The request body containing the policyViolation object. Returns: The raw HTTP response object. `resp_type="response"` is required because a successful PATCH returns 204 No Content, which has no JSON body to decode. """ return self.ms_client.http_request( method="PATCH", url_suffix=url_suffix, json_data=body, resp_type="response", ) """ HELPER FUNCTIONS """ def build_chat_object(raw_response: dict | list, user_id: str = None): """Building chat entry context Getting a list from build_chat_object Args: user_id (str): user id raw_response (dict or list): list of pages Returns: dict or list: output context """ def build_chat(given_chat: dict) -> dict: """ Args: given_chat (dict): Returns: dict: """ # Dicts chat_properties = { "ID": "id", "Subject": "topic", "Created": "createdDateTime", "LastUpdatedTime": "lastUpdatedDateTime", "Type": "chatType", } # Create entry properties entry = {k: given_chat.get(v) for k, v in chat_properties.items()} if user_id: entry["UserID"] = user_id return entry chat_list = [] if isinstance(raw_response, list): # response from list_emails_command for page in raw_response: # raw_response is a list containing multiple pages or one page # if value is not empty, there are emails in the page value = page.get("value") if value: for chat in value: chat_list.append(build_chat(chat)) elif isinstance(raw_response, dict): # response from get_message_command value = raw_response.get("value") if value: for chat in value: chat_list.append(build_chat(chat)) else: return build_chat(raw_response) return chat_list def build_member_object(raw_response: dict | list, chat_id: str) -> dict | list: """Building member entry context Getting a list from build_member_object Args: raw_response (dict or list): list of members chat_id (str): chat id Returns: dict or list: output context """ def build_member(given_member: dict) -> dict: """ Args: given_member (dict): Returns: dict: """ # Dicts member_properties = { "ID": "id", "Name": "displayName", "HistoryStartTime": "visibleHistoryStartDateTime", } # Create entry properties entry = {k: given_member.get(v) for k, v in member_properties.items()} if chat_id: entry["ChatID"] = chat_id return entry member_list = [] if isinstance(raw_response, list): # response from list_emails_command for page in raw_response: # raw_response is a list containing multiple pages or one page # if value is not empty, there are emails in the page value = page.get("value") if value: for member in value: member_list.append(build_member(member)) elif isinstance(raw_response, dict): # response from get_message_command value = raw_response.get("value") if value: for member in value: member_list.append(build_member(member)) else: return build_member(raw_response) return member_list def build_message_object(raw_response: dict | list, chat_id: str) -> dict | list: """Building message entry context Getting a list from build_message_object Args: raw_response (dict or list): list of messages chat_id (str): chat id Returns: dict or list: output context """ def build_message(given_message: dict) -> dict: """ Args: given_message (dict): Returns: dict: """ # Dicts message_properties = { "ID": "id", "Created": "createdDateTime", "LastModifiedTime": "lastModifiedDateTime", } # Create entry properties entry = {k: given_message.get(v) for k, v in message_properties.items()} entry["From"] = given_message.get("from", {}).get("user", {}).get("displayName", "") entry["Body"] = given_message.get("body", {}).get("content", "") if chat_id: entry["ChatID"] = chat_id return entry message_list = [] if isinstance(raw_response, list): # response from list_emails_command for page in raw_response: # raw_response is a list containing multiple pages or one page # if value is not empty, there are emails in the page value = page.get("value") if value: for message in value: if message.get("messageType") == "message" and message.get("from", {}).get("user"): message_list.append(build_message(message)) elif isinstance(raw_response, dict): # response from get_message_command value = raw_response.get("value") if value: for message in value: if message.get("messageType") == "message": message_list.append(build_message(message)) else: return build_message(raw_response) return message_list def build_teams_message_url_suffix( chat_id: str, team_id: str, channel_id: str, message_id: str, parent_message_id: str, payment_model: str, ) -> str: """Build the Microsoft Graph message resource path for a chat, channel, or channel reply. Args: chat_id: The chat ID hosting the message. team_id: The team ID hosting the channel message. channel_id: The channel ID hosting the message. message_id: The ID of the message to update. parent_message_id: The parent message ID when targeting a channel reply. payment_model: The billing model appended as the model query parameter when provided. Returns: The resource path suffix relative to the Graph v1.0 base URL. """ if chat_id: suffix = f"chats/{chat_id}/messages/{message_id}" elif parent_message_id: suffix = f"teams/{team_id}/channels/{channel_id}/messages/{parent_message_id}/replies/{message_id}" else: suffix = f"teams/{team_id}/channels/{channel_id}/messages/{message_id}" if payment_model: suffix = f"{suffix}?model={payment_model}" return suffix def build_policy_violation_body(dlp_action: str, policy_tip_general_text: str, verdict_details: str) -> dict: """Build the request body containing the policyViolation object. Args: dlp_action: The action taken on the message. One of NoAction, BlockAccess, BlockAccessExternal. policy_tip_general_text: The general explanatory text shown to the user in the policy tip. verdict_details: The reviewer actions available on the flagged message. Omitted when empty so Microsoft Graph applies its default (permanent) behavior. Returns: The request body dictionary for the message update. """ policy_violation: dict = { "dlpAction": dlp_action, "policyTip": {"generalText": policy_tip_general_text}, } if verdict_details: policy_violation["verdictDetails"] = verdict_details return {"policyViolation": policy_violation} def validate_teams_message_target_args( chat_id: str, team_id: str, channel_id: str, parent_message_id: str, ) -> None: """Validate the mutually exclusive message-target argument combinations. Args: chat_id: The chat ID hosting the message. team_id: The team ID hosting the channel message. channel_id: The channel ID hosting the message. parent_message_id: The parent message ID when targeting a channel reply. Raises: DemistoException: When the target argument combination is invalid. """ is_channel = bool(team_id or channel_id) if chat_id and parent_message_id: raise DemistoException("'parent_message_id' is only supported for channel messages, not chat messages.") if chat_id and is_channel: raise DemistoException("Provide either 'chat_id' or 'team_id' and 'channel_id', not both.") if parent_message_id and not is_channel: raise DemistoException("A channel reply requires both 'team_id' and 'channel_id'.") if not chat_id and not is_channel: raise DemistoException("A message target is required: either 'chat_id' or 'team_id' and 'channel_id'.") if is_channel and not (team_id and channel_id): raise DemistoException("A channel message requires both 'team_id' and 'channel_id'.") """ COMMANDS """ def test_function(client, _): """ Performs a basic GET request to check if the API is reachable and authentication is successful. Returns ok if successful. """ response = "ok" if demisto.params().get("self_deployed", False): response = "```✅ Success!```" if demisto.command() == "test-module": # cannot use test module due to the lack of ability to set refresh token to integration context # for self deployed app raise Exception( "When using a self-deployed configuration, " "Please enable the integration and run the !msgraph-teams-test command in order to test it" ) # `chats` resolves the chats of the *signed-in user*, so it is delegated-only: Microsoft Graph # rejects it with 400 "Requested API is not supported in application-only context" whenever no # user is present. `delegated_user` is precisely the configured user to act as - it is required # by the community pack and absent from the UCP-driven satellite pack, which is served an # app-only (client credentials) token. So its presence is what decides whether a user-scoped # probe can resolve at all. Without one, fall back to `teams`, the app-only addressable resource # closest to this integration's Teams scope. url_suffix = "chats" if demisto.params().get("delegated_user") else "teams" client.ms_client.http_request(method="GET", url_suffix=url_suffix) # `test-module` is a platform contract: the entry content must be exactly "ok" for the # connection test to be reported as successful. Any other payload - including a decorated # CommandResults - is surfaced to the user as a failure even when the API call succeeded. # `run_command` discards this function's return value, so the result has to be emitted here. # The richer message is kept for the explicit `!msgraph-teams-test` command, which is a # regular command and carries no such constraint. if demisto.command() == "test-module": return_results("ok") else: return_results(CommandResults(readable_output="✅ Success!")) return response, None, None def list_chats_command(client: MsGraphClient, args): user_id = args.get("user_id") odata = args.get("odata") limit = args.get("limit") raw_response = client.list_chats(user_id=user_id, odata=odata, limit=limit) last_page_response = raw_response[len(raw_response) - 1] metadata = "" next_page = last_page_response.get("@odata.nextLink") if next_page: metadata = "\nPay attention there are more results than shown" chat_context = build_chat_object(raw_response, user_id) entry_context = {} if chat_context: entry_context = {"MSGraphTeamsChat(val.ID === obj.ID)": chat_context} if next_page: # .NextPage.indexOf(\'http\')>=0 : will make sure the NextPage token will always be updated because it's a url entry_context["MSGraphTeamsChat(val.NextPage.indexOf('http')>=0)"] = {"NextPage": next_page} # human_readable builder human_readable_header = ( f"{len(chat_context)} chats found {metadata}" if metadata else f"Total of {len(chat_context)} chats found" ) human_readable = tableToMarkdown( human_readable_header, chat_context, headers=["Subject", "Created", "LastUpdatedTime", "Type", "ID"] ) else: human_readable = "### No chats were found" return_outputs(human_readable, entry_context, raw_response) def create_chat_command(client: MsGraphClient, args): user_id = args.get("user_id") subject = args.get("subject") type = args.get("type") members = argToList(args.get("members")) raw_response = client.create_chat(subject, members, user_id=user_id, type=type) chat_context = build_chat_object(raw_response, user_id) entry_context = {} if chat_context: entry_context = {"MSGraphTeamsChat(val.ID === obj.ID)": chat_context} # human_readable builder human_readable_header = f"The chat was created with subject: {subject}" human_readable = tableToMarkdown( human_readable_header, chat_context, headers=["Subject", "Created", "LastUpdatedTime", "Type", "ID"] ) else: human_readable = "### No chats were created" return_outputs(human_readable, entry_context, raw_response) def get_chat_command(client: MsGraphClient, args): user_id = args.get("user_id") chat_id = args.get("chat_id") raw_response = client.get_chat(chat_id, user_id=user_id) chat_context: dict = build_chat_object(raw_response, user_id) entry_context = {} if chat_context: entry_context = {"MSGraphTeamsChat(val.ID === obj.ID)": chat_context} # human_readable builder subject = chat_context.get("Subject") human_readable_header = f"The chat was found, with subject: {subject}" human_readable = tableToMarkdown( human_readable_header, chat_context, headers=["Subject", "Created", "LastUpdatedTime", "Type", "ID"] ) else: human_readable = "### No chats were found" return_outputs(human_readable, entry_context, raw_response) def update_chat_command(client: MsGraphClient, args): chat_id = args.get("chat_id") subject = args.get("subject") raw_response = client.update_chat(chat_id, subject) chat_context = build_chat_object(raw_response) entry_context = {} if chat_context: entry_context = {"MSGraphTeamsChat(val.ID === obj.ID)": chat_context} # human_readable builder human_readable_header = f"The chat was updated with subject: {subject}" human_readable = tableToMarkdown( human_readable_header, chat_context, headers=["Subject", "Created", "LastUpdatedTime", "Type", "ID"] ) else: human_readable = "### No chats were updated" return_outputs(human_readable, entry_context, raw_response) def list_members_command(client: MsGraphClient, args): user_id = args.get("user_id") chat_id = args.get("chat_id") raw_response = client.list_members(chat_id, user_id=user_id) member_context = build_member_object(raw_response, chat_id) entry_context = {} if member_context: entry_context = {"MSGraphTeamsChatMember(val.ID === obj.ID)": member_context} # human_readable builder human_readable_header = f"Total of {len(member_context)} members found" human_readable = tableToMarkdown( human_readable_header, member_context, headers=["Name", "HistoryStartTime", "ID", "ChatID"] ) else: human_readable = "### No members were found" return_outputs(human_readable, entry_context, raw_response) def add_member_command(client: MsGraphClient, args): chat_id = args.get("chat_id") user_id = args.get("user_id") share_history = args.get("share_history") == "true" client.add_member(chat_id, user_id, share_history) human_readable = tableToMarkdown( "Member has been added successfully", {"Chat ID": chat_id, "User ID": user_id, "Share history": share_history}, headers=["Chat ID", "User ID", "Share history"], removeNull=True, ) return_outputs(human_readable) def list_messages_command(client: MsGraphClient, args): user_id = args.get("user_id") chat_id = args.get("chat_id") limit = args.get("limit") raw_response = client.list_messages(chat_id, user_id=user_id, limit=limit) # return_outputs(raw_response) last_page_response = raw_response[len(raw_response) - 1] metadata = "" next_page = last_page_response.get("@odata.nextLink") if next_page: metadata = "\nPay attention there are more results than shown" message_context = build_message_object(raw_response, chat_id) entry_context = {} if message_context: entry_context = {"MSGraphTeamsChatMessage(val.ID === obj.ID)": message_context} if next_page: # .NextPage.indexOf(\'http\')>=0 : will make sure the NextPage token will always be updated because it's a url entry_context["MSGraphTeamsChatMessage(val.NextPage.indexOf('http')>=0)"] = {"NextPage": next_page} # human_readable builder human_readable_header = ( f"{len(message_context)} messages found {metadata}" if metadata else f"Total of {len(message_context)} messages found" ) human_readable = tableToMarkdown( human_readable_header, message_context, headers=["Body", "Created", "From", "ID", "ChatID"] ) else: human_readable = "### No messages were found" return_outputs(human_readable, entry_context, raw_response) def send_message_command(client: MsGraphClient, args): chat_id = args.get("chat_id") body = args.get("body") raw_response = client.send_message(chat_id, body) message_context = build_message_object(raw_response, chat_id) entry_context = {} if message_context: entry_context = {"MSGraphTeamsChatMessage(val.ID === obj.ID)": message_context} # human_readable builder human_readable_header = "The message was send" human_readable = tableToMarkdown( human_readable_header, message_context, headers=["Body", "Created", "From", "ID", "ChatID"] ) else: human_readable = "### No messages were send" return_outputs(human_readable, entry_context, raw_response) def update_teams_message_policy_violation_command(client: MsGraphClient, args: dict) -> None: """Apply a data loss prevention policy violation to a Microsoft Teams message. Targets a chat message, a channel message, or a channel reply. HTTP error responses from Microsoft Graph are surfaced verbatim as a command error by the outer main() try/except. Args: client: The Microsoft Graph client. args: The command arguments. chat_id (Optional): The chat ID hosting the message. team_id (Optional): The team ID hosting the channel message. channel_id (Optional): The channel ID hosting the message. parent_message_id (Optional): The parent message ID when targeting a channel reply. message_id (Required): The ID of the message to update. dlp_action (Optional): The action taken on the message. policy_tip_general_text (Optional): The explanatory text shown to the user. verdict_details (Optional): The reviewer actions available on the flagged message. payment_model (Optional): The billing model appended as the model query parameter. """ chat_id = args.get("chat_id", "") team_id = args.get("team_id", "") channel_id = args.get("channel_id", "") parent_message_id = args.get("parent_message_id", "") message_id = args.get("message_id", "") dlp_action = args.get("dlp_action", "") policy_tip_general_text = args.get("policy_tip_general_text", "") verdict_details = args.get("verdict_details", "") payment_model = args.get("payment_model", "") validate_teams_message_target_args(chat_id, team_id, channel_id, parent_message_id) url_suffix = build_teams_message_url_suffix( chat_id=chat_id, team_id=team_id, channel_id=channel_id, message_id=message_id, parent_message_id=parent_message_id, payment_model=payment_model, ) body = build_policy_violation_body(dlp_action, policy_tip_general_text, verdict_details) client.update_message_policy_violation(url_suffix=url_suffix, body=body) outputs = { "messageId": message_id, "chatId": chat_id, "teamId": team_id, "channelId": channel_id, "parentMessageId": parent_message_id, "dlpAction": dlp_action, "verdictDetails": verdict_details, } outputs = {key: value for key, value in outputs.items() if value} readable_output = tableToMarkdown( name="Teams Message Policy Violation", t=outputs, headerTransform=pascalToSpace, ) return_results( CommandResults( outputs_prefix="MSGraphTeams.TeamsMessagePolicyViolation", outputs_key_field="messageId", outputs=outputs, readable_output=readable_output, ) ) def run_command(commands, command, client: MsGraphClient, args, tries): while tries > 0: try: commands[command](client, demisto.args()) # type: ignore except NotFoundError as err: tries = tries - 1 if tries == 0: return_error(err) else: tries = 0 def run_microsoft_graph_teams_integration(): """COMMANDS MANAGER / SWITCH PANEL Shared entrypoint consumed by every pack that fronts the Microsoft Graph Teams integration (community `MicrosoftGraphTeams` pack + xsoar-supported `MicrosoftGraphTeamsStandardConnector` satellite pack). Each pack's YML declares its own command surface, and the platform routes only YML-declared commands here — so a satellite pack listing a subset of commands cannot invoke the others even though they are dispatched below. """ params: dict = demisto.params() url = params.get("url", "").rstrip("/") + "/v1.0/" tenant: str = params.get("tenant_id", "") auth_and_token_url: str = params.get("client_id", "") enc_key: str = params.get("secret", "") verify: bool = not params.get("insecure", False) self_deployed: bool = params.get("self_deployed", False) redirect_uri: str = params.get("redirect_uri", "") auth_code: str = params.get("auth_code", "") app_name: str = "ms-graph-teams" proxy: bool = params.get("proxy", False) handle_error: bool = argToBoolean(params.get("handle_error", "true")) certificate_thumbprint: str = params.get("certificate_thumbprint", "") private_key: str = params.get("private_key", "") # Under UCP the credential envelope is injected per-request by the platform, # so config-time validation of `enc_key` / certificate is skipped. if not should_use_ucp_auth(): if not self_deployed and not enc_key: raise DemistoException( "Key must be provided. For further information see " "https://xsoar.pan.dev/docs/reference/articles/microsoft-integrations---authentication" ) elif not enc_key and not (certificate_thumbprint and private_key): raise DemistoException("Key or Certificate Thumbprint and Private Key must be provided.") # params related to teams to fetch incidents delegated_user = params.get("delegated_user", "") tries = 3 commands = { "msgraph-teams-test": test_function, "test-module": test_function, "msgraph-teams-list-chats": list_chats_command, "msgraph-teams-create-chat": create_chat_command, "msgraph-teams-get-chat": get_chat_command, "msgraph-teams-update-chat": update_chat_command, "msgraph-teams-list-members": list_members_command, "msgraph-teams-add-member": add_member_command, "msgraph-teams-list-messages": list_messages_command, "msgraph-teams-send-message": send_message_command, } command = demisto.command() demisto.info(f"Command being called is {command}") try: client: MsGraphClient = MsGraphClient( tenant_id=tenant, auth_id=auth_and_token_url, enc_key=enc_key, app_name=app_name, base_url=url, verify=verify, proxy=proxy, self_deployed=self_deployed, redirect_uri=redirect_uri, auth_code=auth_code, handle_error=handle_error, certificate_thumbprint=certificate_thumbprint, private_key=private_key, delegated_user=delegated_user, ) if command == "msgraph-teams-generate-login-url": return_results(generate_login_url(client.ms_client)) elif command == "msgraph-teams-auth-reset": return_results(reset_auth()) elif command == "msgraph-teams-message-update-policy-violation": update_teams_message_policy_violation_command(client, demisto.args()) else: run_command(commands, command, client, demisto.args(), tries) except Exception as e: demisto.debug(traceback.format_exc()) return_error(str(e))