Skip to content

Commit

Permalink
rewrite microsoft teams webhook (demisto#36397)
Browse files Browse the repository at this point in the history
* first commit

* first commit

* add tests

* demo

* demo

* demo

* demo

* demo

* demo

* fixes from cr

* fixes from cr

* Update Packs/MicrosoftTeams/Integrations/MicrosoftTeamsWebhook/MicrosoftTeamsWebhook.yml

Co-authored-by: JudithB <132264628+jbabazadeh@users.noreply.github.com>

* fixes from cr

* fixes from cr

* fixes from cr

* fixes from cr

* fixes from cr

* fixes from cr

* major

---------

Co-authored-by: JudithB <132264628+jbabazadeh@users.noreply.github.com>
  • Loading branch information
tcarmeli1 and jbabazadeh authored Sep 29, 2024
1 parent ae5db0d commit b59e62d
Show file tree
Hide file tree
Showing 8 changed files with 206 additions and 53 deletions.
3 changes: 2 additions & 1 deletion Packs/MicrosoftTeams/.secrets-ignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ MmFiOWM3OTYtMjkwMi00NWY4LWI3MTItN2M1YTYzY2Y0MWM0IyNlZWY5Y2IzNi0wNmRlLTQ2OWItODdj
MmFiOWM3OTYtMjkwMi00NWY4LWI3MTItN2M1YTYzY2Y0MWM0IyNlZWY5Y2IzNi0wNmRlLTQ2OWItODdjZC03MGY0Y2JlMzJkM123=
MmFiOWM3OTYtMjkwMi00NWY4LWI3MTItN2M1YTYzY2Y0MWM0IyNlZWY5Y2IzNi0wNmRlLTQ2OWItODdjZC03MGY0Y2JlMz6Jk45=
MmFiOWM3OTYtMjkwamii00NWY4LWI3MTItN2M1YTYzY2Y0MWM0IyNlZWY5Y2IzNi0wNmRlLTQ2OWItODdjZC03MGY0Y2JlMzJkM123=
MCMjMCMjZGNkMjE5ZGQtYmM2OC00YjliLWJmMGItNGEzM2E3OTZiZTM1IyMxOTowOWRkYzk5MC0zODIxLTRjZWItODAxOS0yNGQzOTk5OGY5M2VfNDhkMzE4ODctNWZhZC00ZDczLWE5ZjUtM2MzNTZlNjhhMDM4QHVucS5nYmwuc3BhY2VzIyM0OGQzMTg4Ny01ZmFkLTRkNzMtYTlmNS0zYzM1NmU2OGEwMzg=
MCMjMCMjZGNkMjE5ZGQtYmM2OC00YjliLWJmMGItNGEzM2E3OTZiZTM1IyMxOTowOWRkYzk5MC0zODIxLTRjZWItODAxOS0yNGQzOTk5OGY5M2VfNDhkMzE4ODctNWZhZC00ZDczLWE5ZjUtM2MzNTZlNjhhMDM4QHVucS5nYmwuc3BhY2VzIyM0OGQzMTg4Ny01ZmFkLTRkNzMtYTlmNS0zYzM1NmU2OGEwMzg=
https://make.powerautomate.com
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

class Client(BaseClient):

def __init__(self, base_url: str, proxy: bool, verify: bool):
def __init__(self, base_url: str, proxy: bool, verify: bool, is_workflow: bool = True):
"""
Client to use in the. Overrides BaseClient.
Expand All @@ -17,6 +17,7 @@ def __init__(self, base_url: str, proxy: bool, verify: bool):
"""
self.base_url = base_url
self.is_workflow = is_workflow
super().__init__(base_url=base_url, proxy=proxy, verify=verify)

def send_teams_message(self, messagecard: dict, adaptive_cards_format: bool = False):
Expand All @@ -28,7 +29,7 @@ def send_teams_message(self, messagecard: dict, adaptive_cards_format: bool = Fa
adaptive_cards_format (bool): Should the adaptive card url format be used?
"""

if adaptive_cards_format:
if adaptive_cards_format or self.is_workflow:
res = self._http_request(
method='POST',
json_data=messagecard,
Expand All @@ -46,36 +47,40 @@ def send_teams_message(self, messagecard: dict, adaptive_cards_format: bool = Fa
demisto.info(f'completed post of message. response text: {res}')


def create_teams_message(message: str, title: str, serverurls: str, adaptive_cards_format: bool = False) -> dict:
def create_teams_message(message: str,
title: str,
serverurls: str,
adaptive_cards_format: bool = False,
is_workflow: bool = True) -> dict:
"""
Creates the Teams message using the messageCard format, and returns the card
Args:
message (str): The message to send in the message card to Teams.
title (str): The title of the message card.
serverurls (str): The URL to send in the message card.
adaptive_cards_format (bool): Should the adaptive cards format be used?
adaptive_cards_format (bool): Should the adaptive cards format be used?.
is_workflow (bool): Is the Microsoft Webhook URL is a workflow.
Returns:
messagecard (dict): dict the adaptive card to send to Teams.
"""

messagecard: dict = {}
if adaptive_cards_format:
messagecard = {
"type": "message",
"attachments": [
{
"contentType": "application/vnd.microsoft.card.adaptive",
"contentUrl": None,
"content": {
"type": "AdaptiveCard",
"body": [
{
"type": "TextBlock",
"size": "Medium",
"weight": "Bolder",
"text": "Cortex XSOAR Notification"
"text": "Cortex XSOAR Notification",
"weight": "bolder",
"size": "medium",
"color": "accent"
},
{
"type": "TextBlock",
Expand All @@ -91,28 +96,49 @@ def create_teams_message(message: str, title: str, serverurls: str, adaptive_car
}
],
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.6"
"version": "1.0"
}
}
]
}
else:
messagecard = {
"@type": "MessageCard",
"@context": "http://schema.org/extensions",
"themeColor": "0076D7",
"summary": "Cortex XSOAR Notification",
"sections": [{
"activityTitle": "Cortex XSOAR Notification",
"activitySubtitle": message,
"markdown": True
}],
"potentialAction": [{
"@type": "OpenUri",
"name": title,
"targets": [{"os": "default", "uri": serverurls}]
}]
}
if is_workflow:
messagecard = {
"type": "message",
"attachments": [
{
"contentType": "application/vnd.microsoft.card.adaptive",
"content": {
"type": "AdaptiveCard",
"body": [
{
"type": "TextBlock",
"text": message
}
],
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.0"
}
}
]
}
else:
messagecard = {
"@type": "MessageCard",
"@context": "http://schema.org/extensions",
"themeColor": "0076D7",
"summary": "Cortex XSOAR Notification",
"sections": [{
"activityTitle": "Cortex XSOAR Notification",
"activitySubtitle": message,
"markdown": True
}],
"potentialAction": [{
"@type": "OpenUri",
"name": title,
"targets": [{"os": "default", "uri": serverurls}]
}]
}

return messagecard

Expand All @@ -131,7 +157,7 @@ def test_module(client: Client, serverurls: str) -> str:
try:
message = "Successful test message from Cortex XSOAR"
title = "Cortex XSOAR Notification"
test_message = create_teams_message(message, title, serverurls)
test_message = create_teams_message(message, title, serverurls, is_workflow=client.is_workflow)
client.send_teams_message(test_message)
return 'ok'
except DemistoException as e:
Expand Down Expand Up @@ -160,7 +186,7 @@ def send_teams_message_command(
which contains the readable_output indicating the message was sent.
"""

messagecard = create_teams_message(message, title, serverurls, adaptive_cards_format)
messagecard = create_teams_message(message, title, serverurls, adaptive_cards_format, is_workflow=client.is_workflow)
client.send_teams_message(messagecard, adaptive_cards_format)
return CommandResults(readable_output='message sent successfully')

Expand Down Expand Up @@ -191,7 +217,8 @@ def main() -> None: # pragma: no cover
client = Client(
base_url=webhook,
verify=verify_certificate,
proxy=proxy
proxy=proxy,
is_workflow='workflow' in webhook
)

if command == 'test-module':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ commonfields:
version: -1
configuration:
- additionalinfo: The webhook URL in the Teams channel.
display: Microsoft Webhook URL
display: Microsoft workflow URL
name: webhookurl
required: true
type: 0
Expand All @@ -24,7 +24,7 @@ configuration:
section: Connect
advanced: true
required: false
description: Integration for sending notifications to a Microsoft Teams channel via Incoming Webhook.
description: Integration for sending notifications to a Microsoft Teams channel via a workflow of type `Post to a channel when a webhook request is received`.
display: Microsoft Teams via Webhook
name: Microsoft Teams via Webhook
script:
Expand All @@ -40,9 +40,14 @@ script:
- defaultValue: Cortex XSOAR URL
description: The title for the link, defaults to "Cortex XSOAR URL".
name: url_title
- description: Should the adaptive cards format be used?
- description: Should the adaptive card format be used or a single text message.
name: adaptive_cards_format
required: false
auto: PREDEFINED
predefined:
- 'true'
- 'false'
defaultValue: true
description: Send a message to Microsoft Teams via Incoming Webhook.
name: ms-teams-message
dockerimage: demisto/python3:3.11.9.105369
Expand Down
Original file line number Diff line number Diff line change
@@ -1,34 +1,35 @@
Use the Microsoft Teams Webhook integration to send messages and notifications to Teams configured with an incoming webhook. The message will always include a link back to the investigation from which it was sent.
Use the Microsoft Teams Webhook integration to send messages and notifications to Teams configured with an incoming webhook or a workflow. When using an adaptive card, the message will always include a link back to the investigation from which it was sent.
## Create an Incoming Webhook on the Team in Microsoft Teams.
For information, see the [Microsoft Create an Incoming Webhook Documentation](https://docs.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook)
## Create a workflow in Microsoft Teams.
First, [Install the Workflows app in Microsoft Teams](https://learn.microsoft.com/en-us/power-automate/teams/install-teams-app).<br/>
Second, [Browse and add workflows in Microsoft Teams Create a workflow to support Teams Webhook](https://support.microsoft.com/en-us/office/browse-and-add-workflows-in-microsoft-teams-4998095c-8b72-4b0e-984c-f2ad39e6ba9a). <br/>
Create a workflow of type `Post to a channel when a webhook request is received` and follow the set up instructions.<br/>
In order to create an instance of the Microsoft Teams Webhook in Cortex XSOAR, complete the following:
To create an instance of the Microsoft Teams Webhook in Cortex XSOAR, complete the following:
1. Add an instance of the integration and add the Webhook URL for the Teams channel.
1. Add an instance of the integration and add the Workflow URL for the Teams channel.
2. Test the integration. If successful, you'll see a test message in the channel.
## Support for Multiple Teams
This integration supports sending messages to additional Teams via an incoming webhook. There are 2 methods for this:
This integration supports sending messages to additional Teams via a workflow. There are 2 methods for this:
- Configure additional integration instances, adding the webhook URL for each Team. You can then send notifications to multiple teams at once, or select the integration instance to use via the playbook task editor.
- Configure additional integration instances, adding the workflow URL for each Team. You can then send notifications to multiple teams at once, or select the integration instance to use via the playbook task editor.
- The ***ms-teams-message*** command includes the *team_webhook* argument, which allows you to pass an alternative webhook URL to override the one from the integration settings.
- The ***ms-teams-message*** command includes the *team_webhook* argument, which allows you to pass an alternative workflow URL to override the one from the integration settings.
You can store the additional webhooks in a Cortex XSOAR list, and use a transformer on the task in a playbook to send to a specific team.
You can store the additional workflow in a Cortex XSOAR list, and use a transformer on the task in a playbook to send to a specific team.
For example, create a list containing a dictionary where the **Key** is the Team, and the **Value** is the webhook for that team.
```
{
"ReadyTeamOne":"webhook url",
"ReadyTeamTwo":"webhook url"
"ReadyTeamOne":"workflow url",
"ReadyTeamTwo":"workflow url"
}
```
You can then pass the list into the *team_webhook* argument, and use the GetField transformer with the value of the Team (i.e., ReadyTeamOne) to retrieve the webhook URL that will be used to override the default from the integration instance, and send the message.
You can then pass the list into the *team_webhook* argument, and use the GetField transformer with the value of the Team (i.e., ReadyTeamOne) to retrieve the workflow URL that will be used to override the default from the integration instance, and send the message. <br/>
For more information, see the [integration documentation](https://xsoar.pan.dev/docs/reference/integrations/microsoft-teams)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from unittest.mock import patch
import pytest
from MicrosoftTeamsWebhook import (Client, send_teams_message_command, create_teams_message)

WEBHOOK = "https://readywebookone"
Expand All @@ -8,7 +10,7 @@
}


fake_client = Client(base_url=WEBHOOK, verify=True, proxy=False)
fake_client = Client(base_url=WEBHOOK, verify=True, proxy=False, is_workflow=False)


def test_create_teams_message_adaptive_cards():
Expand All @@ -20,7 +22,7 @@ def test_create_teams_message_adaptive_cards():


def test_create_teams_message():
message = create_teams_message(MESSAGE, TITLE, SERVERURLS["investigation"])
message = create_teams_message(MESSAGE, TITLE, SERVERURLS["investigation"], is_workflow=False)
assert message
assert message["sections"][0]["activitySubtitle"] == MESSAGE
assert message["potentialAction"][0]["name"] == TITLE
Expand All @@ -45,3 +47,97 @@ def test_test_module(requests_mock):

res = test_module(fake_client, 'fake')
assert res == 'ok'


workflow_client = Client(base_url=WEBHOOK, verify=True, proxy=False, is_workflow=True)


def test_create_teams_message_adaptive_cards_is_workflow():
"""
Given:
- The command arguments with is_workflow = true.
When:
- Executing the create_teams_message function.
Then:
- Verify request message- should use the full adaptive card template.
"""
message = create_teams_message(MESSAGE, TITLE, SERVERURLS["investigation"], True, True)
assert message
assert message["attachments"][0]["content"]["body"][1]["text"] == MESSAGE
assert message["attachments"][0]["content"]["actions"][0]["title"] == TITLE
assert message["attachments"][0]["content"]["actions"][0]["url"] == SERVERURLS["investigation"]


def test_create_teams_message_is_workflow():
"""
Given:
- The command arguments with is_workflow = true.
When:
- Executing the create_teams_message function.
Then:
- Verify request message- should use the only text template.
"""
message = create_teams_message(MESSAGE, TITLE, SERVERURLS["investigation"], is_workflow=True)
assert message
assert message["attachments"][0]["content"]["body"][0]["text"] == MESSAGE
assert message["attachments"][0].get("content", {}).get("actions") is None


def test_send_teams_message_command_is_workflow(requests_mock):
"""
Given:
- The command arguments with is_workflow = true.
When:
- Executing the send_teams_message_command command.
Then:
- Verify when status is 202 we receive `message sent successfully`.
"""
requests_mock.post(WEBHOOK, status_code=202, json={})
res = send_teams_message_command(workflow_client, MESSAGE, TITLE, SERVERURLS["investigation"])
assert res.readable_output == 'message sent successfully'


def test_send_teams_message_command_with_full_adaptivecards_is_workflow(requests_mock):
"""
Given:
- The command arguments with is_workflow = true.
When:
- Executing the send_teams_message_command command.
Then:
- Verify when status is 202 we receive `message sent successfully`.
"""
requests_mock.post(WEBHOOK, status_code=202, json={})
res = send_teams_message_command(workflow_client, MESSAGE, TITLE, SERVERURLS["investigation"], True)
assert res.readable_output == 'message sent successfully'


@pytest.mark.parametrize(
"client,messagecard, adaptive_cards_format, expected_full_url",
[
(fake_client, {"type": "MessageCard", "text": "Hello World"}, False, None),
(fake_client, {"type": "AdaptiveCard", "text": "Hello Adaptive Card"}, True, "https://example.com/webhook"),
(workflow_client, {"type": "AdaptiveCard", "text": "Hello Workflow Card"}, False, "https://example.com/webhook"),
]
)
@patch('MicrosoftTeamsWebhook.demisto.info')
def test_send_teams_message(mock_demisto_info, client, messagecard, adaptive_cards_format, expected_full_url, mocker):
sender = client
http_request = mocker.patch.object(client, '_http_request')
http_request.return_value = "OK"
sender.send_teams_message(messagecard, adaptive_cards_format)
if expected_full_url:
http_request.assert_called_once_with(
method='POST',
json_data=messagecard,
raise_on_status=True,
resp_type='text',
full_url=sender.base_url
)
else:
http_request.assert_called_once_with(
method='POST',
json_data=messagecard,
raise_on_status=True,
resp_type='text'
)
mock_demisto_info.assert_called_once_with('completed post of message. response text: OK')
Loading

0 comments on commit b59e62d

Please sign in to comment.