diff --git a/plugins/microsoft_teams/.CHECKSUM b/plugins/microsoft_teams/.CHECKSUM index ce9c69bd74..193038d862 100644 --- a/plugins/microsoft_teams/.CHECKSUM +++ b/plugins/microsoft_teams/.CHECKSUM @@ -1,7 +1,7 @@ { - "spec": "c789730bf7c262c32f372f8e86753df8", - "manifest": "8a7b4cfc9567a23d50706d49807dc753", - "setup": "a75c05ebbf3c01d5d5da5687a9bd0036", + "spec": "c9becfea96f7b49ec9f3f739d57a402d", + "manifest": "cf57f82d2fd650136689da39f1359bba", + "setup": "07ef2d2945cdd65380bb0e3d8e4d3a4f", "schemas": [ { "identifier": "add_channel_to_team/schema.py", @@ -19,6 +19,10 @@ "identifier": "add_member_to_team/schema.py", "hash": "daf644fdedf69570b4de8d3c8b5d4692" }, + { + "identifier": "create_teams_chat/schema.py", + "hash": "624f57b6ee026fa69e74e6ba2ebf732b" + }, { "identifier": "create_teams_enabled_group/schema.py", "hash": "9d1a41cc30c7af5e5c61841b586f0a3b" @@ -33,20 +37,24 @@ }, { "identifier": "get_message_in_channel/schema.py", - "hash": "435e1e4e395f4bb806768e88c09ab444" + "hash": "b8b1956f659ff8070bf11ac27f20e1dc" }, { "identifier": "get_message_in_chat/schema.py", - "hash": "bfa7d0852b744793ab8ba7e935263629" + "hash": "7084e6609fae0c1da35859d3507cf0e2" }, { "identifier": "get_reply_list/schema.py", - "hash": "ae7a21cb4211617fe55bf557192e6841" + "hash": "d299f50370d5d1672687faf0defa92be" }, { "identifier": "get_teams/schema.py", "hash": "d4167388ba5726a8f13639844294cee5" }, + { + "identifier": "list_messages_in_chat/schema.py", + "hash": "b20a85b662826aabdd19c0fa6c55aecc" + }, { "identifier": "remove_channel_from_team/schema.py", "hash": "02305968869d8a40c21c7e2176862ddc" diff --git a/plugins/microsoft_teams/bin/icon_microsoft_teams b/plugins/microsoft_teams/bin/icon_microsoft_teams index d03caffb4a..4a971de1f4 100644 --- a/plugins/microsoft_teams/bin/icon_microsoft_teams +++ b/plugins/microsoft_teams/bin/icon_microsoft_teams @@ -6,7 +6,7 @@ from sys import argv Name = "Microsoft Teams" Vendor = "rapid7" -Version = "5.1.0" +Version = "6.0.0" Description = "The Microsoft Teams plugin allows you to send and trigger workflows on new messages. The plugin will also allow for teams management with the ability to add and remove teams, channels, and users" @@ -68,6 +68,10 @@ def main(): self.add_action(actions.GetReplyList()) + self.add_action(actions.ListMessagesInChat()) + + self.add_action(actions.CreateTeamsChat()) + """Run plugin""" cli = insightconnect_plugin_runtime.CLI(ICONMicrosoftTeams()) diff --git a/plugins/microsoft_teams/help.md b/plugins/microsoft_teams/help.md index 4d7f2d5992..5efefe4c42 100644 --- a/plugins/microsoft_teams/help.md +++ b/plugins/microsoft_teams/help.md @@ -193,6 +193,61 @@ Example output: } ``` +#### Create Teams Chat + +Create a chat in Microsoft Teams + +##### Input + +|Name|Type|Default|Required|Description|Enum|Example| +| :--- | :--- | :--- | :--- | :--- | :--- | :--- | +|members|[]itemMember|None|True|A list of usernames to set as members|None|[{"user_info": "user@example.com", "role": "owner"}, {"user_info": "ab123bcd-123a-412a3-abc1-a123456b789c", "role": "owner"}]| +|topic|string|None|False|Topic of chat to be added (only available for group chats)|None|example_topic| + +Example input: + +``` +{ + "members": [ + { + "Role": "owner", + "User Info": "user@example.com" + }, + { + "Role": "owner", + "User Info": "ab123bcd-123a-412a3-abc1-a123456b789c" + }, + { + "Role": "owner", + "User Info": "cb123bcd-123a-412a3-abc1-a123456b789c" + } + ], + "topic": "example_topic" +} +``` + +##### Output + +|Name|Type|Required|Description|Example| +| :--- | :--- | :--- | :--- | :--- | +|chat|itemChat|False|Information about the chat that was created|{'@odata.context': 'https://graph.microsoft.com/beta/$metadata#chats/$entity', 'chatType': 'group', 'createdDateTime': '2023-11-09T12:07:43.167Z', 'id': '12:a12345bc678d901e12345f67890g1234_thread.v2', 'lastUpdatedDateTime': '2023-11-09T12:07:43.167Z', 'tenantId': '1a234567-bc8d-9e01-23fg-4h567i8j9k01', 'webUrl': 'https://teams.microsoft.com/l/chat/12%3AA12345BC678D901E12345F67890G1234%40thread.v2/0tenantId=1a234567-bc8d-9e01-23fg-4h567i8j9k01'}| + +Example output: + +``` +{ + "chat": { + "@odata.context": "https://graph.microsoft.com/beta/$metadata#chats/$entity", + "chatType": "group", + "createdDateTime": "2023-11-09T12:07:43.167Z", + "id": "12:a12345bc678d901e12345f67890g1234_thread.v2", + "lastUpdatedDateTime": "2023-11-09T12:07:43.167Z", + "tenantId": "1a234567-bc8d-9e01-23fg-4h567i8j9k01", + "webUrl": "https://teams.microsoft.com/l/chat/12%3AA12345BC678D901E12345F67890G1234%40thread.v2/0tenantId=1a234567-bc8d-9e01-23fg-4h567i8j9k01" + } +} +``` + #### Create Teams Enabled Group Create a group in Azure and enable it for Microsoft Teams @@ -543,6 +598,63 @@ Example output: } ``` +#### List Messages from a Chat + +Retrieve up to the last 50 messages in a chat + +##### Input + +|Name|Type|Default|Required|Description|Enum|Example| +| :--- | :--- | :--- | :--- | :--- | :--- | :--- | +|chat_id|string|None|True|The ID of chat|None|11:examplechat.name| + +Example input: + +``` +{ + "chat_id": "11:examplechat.name" +} +``` + +##### Output + +|Name|Type|Required|Description|Example| +| :--- | :--- | :--- | :--- | :--- | +|messages|[]chatMessage|False|The message object that was created|[{"attachments": [], "body": {"content": "Test message", "contentType": "text"}, "chatId": "12:a12345bc678d901e12345f67890g1234_thread.v2", "createdDateTime": "2023-11-08T10:38:18.048Z", "etag": "1234567890123", "from": {"user": {"@odata.type": "#microsoft.graph.teamworkUserIdentity", "displayName": "Test User", "id": "8a234567-bc8d-9e01-23fg-4h567i8j9k98", "tenantId": "1a234567-bc8d-9e01-23fg-4h567i8j9k01", "userIdentityType": "aadUser"}}, "id": "1234567890123", "importance": "normal", "lastModifiedDateTime": "2023-11-08T10:38:18.048Z", "locale": "en-us", "mentions": [], "messageType": "message", "reactions": []}]| + +Example output: + +``` +{ + "messages": { + "attachments": [], + "body": { + "content": "Test message", + "contentType": "text" + }, + "chatId": "12:a12345bc678d901e12345f67890g1234_thread.v2", + "createdDateTime": "2023-11-08T10:38:18.048Z", + "etag": "1234567890123", + "from": { + "user": { + "@odata.type": "#microsoft.graph.teamworkUserIdentity", + "displayName": "Test User", + "id": "8a234567-bc8d-9e01-23fg-4h567i8j9k98", + "tenantId": "1a234567-bc8d-9e01-23fg-4h567i8j9k01", + "userIdentityType": "aadUser" + } + }, + "id": "1234567890123", + "importance": "normal", + "lastModifiedDateTime": "2023-11-08T10:38:18.048Z", + "locale": "en-us", + "mentions": [], + "messageType": "message", + "reactions": [] + } +} +``` + #### Remove Channel from Team Remove a channel from a team @@ -1004,7 +1116,7 @@ Example output: |Created Date Time|string|None|False|Created date time|None| |Deleted Date Time|string|None|False|Deleted date time|None| |Etag|string|None|False|Version number of the chat message|None| -|Event Detail|string|None|False|Represents details of an event that happened in a chat|None| +|Event Detail|object|None|False|Represents details of an event that happened in a chat|None| |From|from|None|False|Details of the sender of the chat message|None| |ID|string|None|False|Unique ID of the message|None| |Importance|string|None|False|The importance of the chat message|None| @@ -1020,6 +1132,25 @@ Example output: |Subject|string|None|False|The subject of the chat message, in plaintext|None| |Summary|string|None|False|Summary text of the chat message that could be used for push notifications and summary views or fall back views|None| |Web URL|string|None|False|Link to the message in Microsoft Teams|None| + +**itemMember** + +|Name|Type|Default|Required|Description|Example| +| :--- | :--- | :--- | :--- | :--- | :--- | +|Role|string|None|True|The role of the user to be added to the chat|None| +|User Info|string|None|True|The ID or Email address|None| + +**itemChat** + +|Name|Type|Default|Required|Description|Example| +| :--- | :--- | :--- | :--- | :--- | :--- | +|Data Context|string|None|False|The context of the newly created chat|None| +|Chat Type|string|None|False|The type of the newly created chat|None| +|Created Date Time|string|None|False|Created date time|None| +|Chat ID|string|None|False|The ID of the newly created chat|None| +|Last Updated Date Time|string|None|False|Last updated date time|None| +|Tenant ID|string|None|False|The ID the directory that he newly created chat is in|None| +|Web URL|string|None|False|The URL of the newly created chat|None| ## Troubleshooting @@ -1028,6 +1159,7 @@ If there is more than one team with the same name in your organization, the olde # Version History +* 6.0.0 - New actions: `create_teams_chat` | `list_messages_in_chat` | update type of `Event Detail` to type object * 5.1.0 - New actions: Get Reply List | Improve typing on message * 5.0.0 - New actions: Get Message in Chat, Get Message in Channel | Update to latest SDK version | Change required fields in message schema * 4.2.0 - New Message Received: Fixed issue where `font-size` value appeared in the `urls`, and `domains` output fields | Can choose the role of a member when adding them to a channel | Fix bug where case-sensitive URLs were returned in lower case | Improved reliability of domains output value diff --git a/plugins/microsoft_teams/icon_microsoft_teams/actions/__init__.py b/plugins/microsoft_teams/icon_microsoft_teams/actions/__init__.py index 7509f8d04d..82632e5d56 100755 --- a/plugins/microsoft_teams/icon_microsoft_teams/actions/__init__.py +++ b/plugins/microsoft_teams/icon_microsoft_teams/actions/__init__.py @@ -31,3 +31,8 @@ from .get_message_in_chat.action import GetMessageInChat from .get_reply_list.action import GetReplyList + +from .list_messages_in_chat.action import ListMessagesInChat + +from .create_teams_chat.action import CreateTeamsChat + diff --git a/plugins/microsoft_teams/icon_microsoft_teams/actions/create_teams_chat/__init__.py b/plugins/microsoft_teams/icon_microsoft_teams/actions/create_teams_chat/__init__.py new file mode 100644 index 0000000000..13e1f8e79e --- /dev/null +++ b/plugins/microsoft_teams/icon_microsoft_teams/actions/create_teams_chat/__init__.py @@ -0,0 +1,2 @@ +# GENERATED BY INSIGHT-PLUGIN - DO NOT EDIT +from .action import CreateTeamsChat diff --git a/plugins/microsoft_teams/icon_microsoft_teams/actions/create_teams_chat/action.py b/plugins/microsoft_teams/icon_microsoft_teams/actions/create_teams_chat/action.py new file mode 100644 index 0000000000..7b14dbc768 --- /dev/null +++ b/plugins/microsoft_teams/icon_microsoft_teams/actions/create_teams_chat/action.py @@ -0,0 +1,25 @@ +import insightconnect_plugin_runtime +from .schema import CreateTeamsChatInput, CreateTeamsChatOutput, Input, Output, Component + + +# Custom imports below +from icon_microsoft_teams.util.komand_clean_with_nulls import remove_null_and_clean +from icon_microsoft_teams.util.teams_utils import create_chat + + +class CreateTeamsChat(insightconnect_plugin_runtime.Action): + def __init__(self): + super(self.__class__, self).__init__( + name="create_teams_chat", + description=Component.DESCRIPTION, + input=CreateTeamsChatInput(), + output=CreateTeamsChatOutput(), + ) + + def run(self, params={}): + members = params.get(Input.MEMBERS) + topic = params.get(Input.TOPIC) + + group_result = create_chat(self.connection, members, topic) + + return {Output.CHAT: remove_null_and_clean(group_result)} diff --git a/plugins/microsoft_teams/icon_microsoft_teams/actions/create_teams_chat/schema.py b/plugins/microsoft_teams/icon_microsoft_teams/actions/create_teams_chat/schema.py new file mode 100644 index 0000000000..ea02ab9f94 --- /dev/null +++ b/plugins/microsoft_teams/icon_microsoft_teams/actions/create_teams_chat/schema.py @@ -0,0 +1,146 @@ +# GENERATED BY INSIGHT-PLUGIN - DO NOT EDIT +import insightconnect_plugin_runtime +import json + + +class Component: + DESCRIPTION = "Create a chat in Microsoft Teams" + + +class Input: + MEMBERS = "members" + TOPIC = "topic" + + +class Output: + CHAT = "chat" + + +class CreateTeamsChatInput(insightconnect_plugin_runtime.Input): + schema = json.loads(r""" + { + "type": "object", + "title": "Variables", + "properties": { + "members": { + "type": "array", + "title": "Members", + "description": "A list of usernames to set as members", + "items": { + "$ref": "#/definitions/itemMember" + }, + "order": 2 + }, + "topic": { + "type": "string", + "title": "Group Topic", + "description": "Topic of chat to be added (only available for group chats)", + "order": 1 + } + }, + "required": [ + "members" + ], + "definitions": { + "itemMember": { + "type": "object", + "title": "itemMember", + "properties": { + "user_info": { + "type": "string", + "title": "User Info", + "description": "The ID or Email address", + "order": 1 + }, + "role": { + "type": "string", + "title": "Role", + "description": "The role of the user to be added to the chat", + "enum": [ + "owner", + "guest" + ], + "order": 2 + } + }, + "required": [ + "role", + "user_info" + ] + } + } +} + """) + + def __init__(self): + super(self.__class__, self).__init__(self.schema) + + +class CreateTeamsChatOutput(insightconnect_plugin_runtime.Output): + schema = json.loads(r""" + { + "type": "object", + "title": "Variables", + "properties": { + "chat": { + "$ref": "#/definitions/itemChat", + "title": "Chat", + "description": "Information about the chat that was created", + "order": 1 + } + }, + "definitions": { + "itemChat": { + "type": "object", + "title": "itemChat", + "properties": { + "@odata.context": { + "type": "string", + "title": "Data Context", + "description": "The context of the newly created chat", + "order": 1 + }, + "chatType": { + "type": "string", + "title": "Chat Type", + "description": "The type of the newly created chat", + "order": 2 + }, + "createdDateTime": { + "type": "string", + "title": "Created Date Time", + "description": "Created date time", + "order": 3 + }, + "id": { + "type": "string", + "title": "Chat ID", + "description": "The ID of the newly created chat", + "order": 4 + }, + "lastUpdatedDateTime": { + "type": "string", + "title": "Last Updated Date Time", + "description": "Last updated date time", + "order": 5 + }, + "tenantId": { + "type": "string", + "title": "Tenant ID", + "description": "The ID the directory that he newly created chat is in", + "order": 6 + }, + "webUrl": { + "type": "string", + "title": "Web URL", + "description": "The URL of the newly created chat", + "order": 7 + } + } + } + } +} + """) + + def __init__(self): + super(self.__class__, self).__init__(self.schema) diff --git a/plugins/microsoft_teams/icon_microsoft_teams/actions/get_message_in_channel/schema.py b/plugins/microsoft_teams/icon_microsoft_teams/actions/get_message_in_channel/schema.py index 4541ebb9f2..bdf2364b2b 100644 --- a/plugins/microsoft_teams/icon_microsoft_teams/actions/get_message_in_channel/schema.py +++ b/plugins/microsoft_teams/icon_microsoft_teams/actions/get_message_in_channel/schema.py @@ -126,7 +126,7 @@ class GetMessageInChannelOutput(insightconnect_plugin_runtime.Output): "order": 7 }, "eventDetail": { - "type": "string", + "type": "object", "title": "Event Detail", "description": "Represents details of an event that happened in a chat", "order": 8 diff --git a/plugins/microsoft_teams/icon_microsoft_teams/actions/get_message_in_chat/schema.py b/plugins/microsoft_teams/icon_microsoft_teams/actions/get_message_in_chat/schema.py index 5515355eb3..5e47c9af9a 100644 --- a/plugins/microsoft_teams/icon_microsoft_teams/actions/get_message_in_chat/schema.py +++ b/plugins/microsoft_teams/icon_microsoft_teams/actions/get_message_in_chat/schema.py @@ -119,7 +119,7 @@ class GetMessageInChatOutput(insightconnect_plugin_runtime.Output): "order": 7 }, "eventDetail": { - "type": "string", + "type": "object", "title": "Event Detail", "description": "Represents details of an event that happened in a chat", "order": 8 diff --git a/plugins/microsoft_teams/icon_microsoft_teams/actions/get_reply_list/schema.py b/plugins/microsoft_teams/icon_microsoft_teams/actions/get_reply_list/schema.py index 59dc44051c..257da89a76 100644 --- a/plugins/microsoft_teams/icon_microsoft_teams/actions/get_reply_list/schema.py +++ b/plugins/microsoft_teams/icon_microsoft_teams/actions/get_reply_list/schema.py @@ -122,7 +122,7 @@ class GetReplyListOutput(insightconnect_plugin_runtime.Output): "order": 7 }, "eventDetail": { - "type": "string", + "type": "object", "title": "Event Detail", "description": "Represents details of an event that happened in a chat", "order": 8 diff --git a/plugins/microsoft_teams/icon_microsoft_teams/actions/list_messages_in_chat/__init__.py b/plugins/microsoft_teams/icon_microsoft_teams/actions/list_messages_in_chat/__init__.py new file mode 100644 index 0000000000..575618058e --- /dev/null +++ b/plugins/microsoft_teams/icon_microsoft_teams/actions/list_messages_in_chat/__init__.py @@ -0,0 +1,2 @@ +# GENERATED BY INSIGHT-PLUGIN - DO NOT EDIT +from .action import ListMessagesInChat diff --git a/plugins/microsoft_teams/icon_microsoft_teams/actions/list_messages_in_chat/action.py b/plugins/microsoft_teams/icon_microsoft_teams/actions/list_messages_in_chat/action.py new file mode 100644 index 0000000000..d7e4d2d777 --- /dev/null +++ b/plugins/microsoft_teams/icon_microsoft_teams/actions/list_messages_in_chat/action.py @@ -0,0 +1,22 @@ +import insightconnect_plugin_runtime +from .schema import ListMessagesInChatInput, ListMessagesInChatOutput, Input, Output, Component +from icon_microsoft_teams.util.teams_utils import list_messages_from_chat + +# Custom imports below + + +class ListMessagesInChat(insightconnect_plugin_runtime.Action): + def __init__(self): + super(self.__class__, self).__init__( + name="list_messages_in_chat", + description=Component.DESCRIPTION, + input=ListMessagesInChatInput(), + output=ListMessagesInChatOutput(), + ) + + def run(self, params={}): + chat_id = params.get(Input.CHAT_ID, "") + + messages = list_messages_from_chat(self.connection, chat_id) + + return {Output.MESSAGES: messages} diff --git a/plugins/microsoft_teams/icon_microsoft_teams/actions/list_messages_in_chat/schema.py b/plugins/microsoft_teams/icon_microsoft_teams/actions/list_messages_in_chat/schema.py new file mode 100644 index 0000000000..dbfe604871 --- /dev/null +++ b/plugins/microsoft_teams/icon_microsoft_teams/actions/list_messages_in_chat/schema.py @@ -0,0 +1,310 @@ +# GENERATED BY INSIGHT-PLUGIN - DO NOT EDIT +import insightconnect_plugin_runtime +import json + + +class Component: + DESCRIPTION = "Retrieve up to the last 50 messages in a chat" + + +class Input: + CHAT_ID = "chat_id" + + +class Output: + MESSAGES = "messages" + + +class ListMessagesInChatInput(insightconnect_plugin_runtime.Input): + schema = json.loads(r""" + { + "type": "object", + "title": "Variables", + "properties": { + "chat_id": { + "type": "string", + "title": "Chat ID", + "description": "The ID of chat", + "order": 1 + } + }, + "required": [ + "chat_id" + ], + "definitions": {} +} + """) + + def __init__(self): + super(self.__class__, self).__init__(self.schema) + + +class ListMessagesInChatOutput(insightconnect_plugin_runtime.Output): + schema = json.loads(r""" + { + "type": "object", + "title": "Variables", + "properties": { + "messages": { + "type": "array", + "title": "Message", + "description": "The message object that was created", + "items": { + "$ref": "#/definitions/chatMessage" + }, + "order": 1 + } + }, + "definitions": { + "chatMessage": { + "type": "object", + "title": "chatMessage", + "properties": { + "attachments": { + "type": "array", + "title": "Attachments", + "description": "References to attached objects", + "items": { + "type": "object" + }, + "order": 1 + }, + "body": { + "$ref": "#/definitions/itemBody", + "title": "Body", + "description": "Representation of the content of the chat message", + "order": 2 + }, + "chatId": { + "type": "string", + "title": "Chat ID", + "description": "Represents the identity of the chat", + "order": 3 + }, + "channelIdentity": { + "$ref": "#/definitions/channelIdentity", + "title": "Channel Identity", + "description": "Represents identity of the channel", + "order": 4 + }, + "createdDateTime": { + "type": "string", + "title": "Created Date Time", + "description": "Created date time", + "order": 5 + }, + "deletedDateTime": { + "type": "string", + "title": "Deleted Date Time", + "description": "Deleted date time", + "order": 6 + }, + "etag": { + "type": "string", + "title": "Etag", + "description": "Version number of the chat message", + "order": 7 + }, + "eventDetail": { + "type": "object", + "title": "Event Detail", + "description": "Represents details of an event that happened in a chat", + "order": 8 + }, + "from": { + "$ref": "#/definitions/from", + "title": "From", + "description": "Details of the sender of the chat message", + "order": 9 + }, + "id": { + "type": "string", + "title": "ID", + "description": "Unique ID of the message", + "order": 10 + }, + "importance": { + "type": "string", + "title": "Importance", + "description": "The importance of the chat message", + "order": 11 + }, + "lastModifiedDateTime": { + "type": "string", + "title": "Last Modified Date Time", + "description": "Timestamp when the chat message is created (initial setting) or modified", + "order": 12 + }, + "lastEditedDateTime": { + "type": "string", + "title": "Last Edited Date Time", + "description": "Timestamp when edits to the chat message were made", + "order": 13 + }, + "locale": { + "type": "string", + "title": "Locale", + "description": "Locale of the chat message set by the client", + "order": 14 + }, + "mentions": { + "type": "array", + "title": "Mentions", + "description": "List of entities mentioned in the chat message", + "items": { + "type": "object" + }, + "order": 15 + }, + "messageHistory": { + "type": "array", + "title": "Message History", + "description": "List of activity history of a message item", + "items": { + "type": "object" + }, + "order": 16 + }, + "messageType": { + "type": "string", + "title": "Message Type", + "description": "The type of chat message", + "order": 17 + }, + "policyViolation": { + "type": "object", + "title": "Policy Violation", + "description": "Defines the properties of a policy violation", + "order": 18 + }, + "reactions": { + "type": "array", + "title": "Reactions", + "description": "Reactions for this chat message", + "items": { + "$ref": "#/definitions/chatMessageReaction" + }, + "order": 19 + }, + "replyToId": { + "type": "string", + "title": "Reply To ID", + "description": "ID of the parent chat message or root chat message of the thread", + "order": 20 + }, + "subject": { + "type": "string", + "title": "Subject", + "description": "The subject of the chat message, in plaintext", + "order": 21 + }, + "summary": { + "type": "string", + "title": "Summary", + "description": "Summary text of the chat message that could be used for push notifications and summary views or fall back views", + "order": 22 + }, + "webUrl": { + "type": "string", + "title": "Web URL", + "description": "Link to the message in Microsoft Teams", + "order": 23 + } + } + }, + "itemBody": { + "type": "object", + "title": "itemBody", + "properties": { + "content": { + "type": "string", + "title": "Content", + "description": "The content of the item", + "order": 1 + }, + "contentType": { + "type": "string", + "title": "Content Type", + "description": "The type of the content, possible values are text and HTML", + "order": 2 + } + } + }, + "channelIdentity": { + "type": "object", + "title": "channelIdentity", + "properties": { + "channelId": { + "type": "string", + "title": "Channel ID", + "description": "The identity of the channel in which the message was posted", + "order": 1 + }, + "teamId": { + "type": "string", + "title": "Team ID", + "description": "The identity of the team in which the message was posted", + "order": 2 + } + } + }, + "from": { + "type": "object", + "title": "from", + "properties": { + "user": { + "$ref": "#/definitions/user", + "title": "User", + "description": "User", + "order": 1 + } + } + }, + "user": { + "type": "object", + "title": "user", + "properties": { + "displayName": { + "type": "string", + "title": "Display name", + "description": "Display name", + "order": 1 + }, + "id": { + "type": "string", + "title": "ID", + "description": "ID", + "order": 2 + } + } + }, + "chatMessageReaction": { + "type": "object", + "title": "chatMessageReaction", + "properties": { + "createdDateTime": { + "type": "string", + "title": "Created Date Time", + "description": "Created date time", + "order": 1 + }, + "reactionType": { + "type": "string", + "title": "Reaction Type", + "description": "Reaction Type", + "order": 2 + }, + "user": { + "$ref": "#/definitions/user", + "title": "User", + "description": "The user who reacted to the message", + "order": 3 + } + } + } + } +} + """) + + def __init__(self): + super(self.__class__, self).__init__(self.schema) diff --git a/plugins/microsoft_teams/icon_microsoft_teams/util/teams_utils.py b/plugins/microsoft_teams/icon_microsoft_teams/util/teams_utils.py index 9e3024f222..799d1aad14 100644 --- a/plugins/microsoft_teams/icon_microsoft_teams/util/teams_utils.py +++ b/plugins/microsoft_teams/icon_microsoft_teams/util/teams_utils.py @@ -227,6 +227,56 @@ def send_html_message( return message +def create_chat(connection: insightconnect_plugin_runtime.connection, members: list, topic: str) -> dict: + """ + Creates a chat for given users + + :param connection: Object (insightconnect_plugin_runtime.connection) + :param members: list + :return: dict + """ + + create_chat_endpoint = "https://graph.microsoft.com/beta/chats/" + create_chat_payload = {} + + list_members = [] + for member in members: + formatted_member = { + "@odata.type": "#microsoft.graph.aadUserConversationMember", + "roles": [f"{member.get('role')}"], + "user@odata.bind": f"https://graph.microsoft.com/v1.0/users('{member.get('user_info')}')", + } + list_members.append(formatted_member) + + if len(list_members) == 2: + create_chat_payload["chatType"] = "oneOnOne" + elif len(list_members) > 2: + create_chat_payload["chatType"] = "group" + if topic: + create_chat_payload["topic"] = topic + else: + raise PluginException(cause="Create chat failed.", assistance="Less than 2 valid members were provided") + + create_chat_payload["members"] = list_members + + headers = connection.get_headers() + + result = requests.post(create_chat_endpoint, json=create_chat_payload, headers=headers) # nosec B113 + + try: + result.raise_for_status() + except Exception as error: + raise PluginException(cause="Create chat failed.", assistance=result.text, data=error) from error + + if not result.status_code == 201: + raise PluginException(cause="Create channel returned an unexpected result.", assistance=result.text) + + try: + return result.json() + except Exception as error: + raise PluginException(preset=PluginException.Preset.INVALID_JSON, data=error) + + def create_channel( logger: Logger, connection: insightconnect_plugin_runtime.connection, @@ -351,6 +401,38 @@ def get_message_from_chat( # noqa: C901 return remove_null_and_clean(message) +def list_messages_from_chat(connection: insightconnect_plugin_runtime.connection, chat_id: str) -> list: # noqa: C901 + """ + Lists the last 50 messages from a teams chat + + :param connection: Object (insightconnect_plugin_runtime.connection) + :param chat_id: String + :return: list + """ + + params = {"$top": 50, "$orderby": "createdDateTime desc"} + message_url = f"https://graph.microsoft.com/beta/{connection.tenant_id}/chats/{chat_id}/messages/" + headers = connection.get_headers() + response = requests.get(message_url, headers=headers, params=params) # nosec B113 + + try: + response.raise_for_status() + messages = response.json().get("value", []) + except requests.HTTPError as http_error: + raise PluginException(preset=PluginException.Preset.BAD_REQUEST, data=http_error) + except ValueError as value_error: + raise PluginException(preset=PluginException.Preset.INVALID_JSON, data=value_error) + except Exception as error: + raise PluginException(preset=PluginException.Preset.UNKNOWN, data=error) + + clean_messages = [] + + for message in messages: + clean_messages.append(clean(message)) + + return clean_messages + + def get_reply_list( # noqa: C901 connection: insightconnect_plugin_runtime.connection, team_id: str, diff --git a/plugins/microsoft_teams/plugin.spec.yaml b/plugins/microsoft_teams/plugin.spec.yaml index 4d67636dd1..81b450bf16 100644 --- a/plugins/microsoft_teams/plugin.spec.yaml +++ b/plugins/microsoft_teams/plugin.spec.yaml @@ -4,7 +4,7 @@ products: [insightconnect] name: microsoft_teams title: Microsoft Teams description: The Microsoft Teams plugin allows you to send and trigger workflows on new messages. The plugin will also allow for teams management with the ability to add and remove teams, channels, and users -version: 5.1.0 +version: 6.0.0 supported_versions: ["Microsoft Graph API v1.0 2021-11-28"] vendor: rapid7 support: community @@ -330,7 +330,7 @@ types: required: false eventDetail: title: "Event Detail" - type: string + type: object description: "Represents details of an event that happened in a chat" required: false from: @@ -408,7 +408,61 @@ types: type: string description: "Link to the message in Microsoft Teams" required: false - + itemMember: + user_info: + title: User Info + type: string + description: The ID or Email address + required: true + role: + title: Role + type: string + description: The role of the user to be added to the chat + required: true + enum: + - owner + - guest + itemChat: + "@odata.context": + title: Data Context + type: string + description: The context of the newly created chat + required: false + chatType: + title: Chat Type + type: string + description: The type of the newly created chat + required: false + createdDateTime: + title: Created Date Time + type: string + description: Created date time + required: false + id: + title: Chat ID + type: string + description: The ID of the newly created chat + required: false + lastUpdatedDateTime: + title: Last Updated Date Time + type: string + description: Last updated date time + required: false + chatType: + title: Chat Type + type: string + description: The type of the newly created chat + required: false + tenantId: + title: Tenant ID + type: string + description: The ID the directory that he newly created chat is in + required: false + webUrl: + title: Web URL + type: string + description: The URL of the newly created chat + required: false connection: application_id: title: Application ID @@ -985,3 +1039,43 @@ actions: description: The message object that was created type: "[]chatMessage" required: false + list_messages_in_chat: + title: List Messages from a Chat + description: Retrieve up to the last 50 messages in a chat + input: + chat_id: + title: Chat ID + description: The ID of chat + type: string + required: true + example: 11:examplechat.name + output: + messages: + title: Message + description: The message object that was created + type: "[]chatMessage" + required: false + example: [{"attachments": [],"body": {"content": "Test message","contentType": "text"},"chatId": "12:a12345bc678d901e12345f67890g1234_thread.v2","createdDateTime": "2023-11-08T10:38:18.048Z","etag": "1234567890123","from": {"user": {"@odata.type": "#microsoft.graph.teamworkUserIdentity","displayName": "Test User","id": "8a234567-bc8d-9e01-23fg-4h567i8j9k98","tenantId": "1a234567-bc8d-9e01-23fg-4h567i8j9k01","userIdentityType": "aadUser"}},"id": "1234567890123","importance": "normal","lastModifiedDateTime": "2023-11-08T10:38:18.048Z","locale": "en-us","mentions": [],"messageType": "message","reactions": []}] + create_teams_chat: + title: Create Teams Chat + description: Create a chat in Microsoft Teams + input: + topic: + title: Group Topic + description: Topic of chat to be added (only available for group chats) + type: string + required: false + example: example_topic + members: + title: Members + description: A list of usernames to set as members + type: "[]itemMember" + required: true + example: [{"user_info": "user@example.com","role": "owner"},{"user_info": "ab123bcd-123a-412a3-abc1-a123456b789c","role": "owner"}] + output: + chat: + title: Chat + description: Information about the chat that was created + type: itemChat + required: false + example: {"@odata.context": "https://graph.microsoft.com/beta/$metadata#chats/$entity","chatType": "group","createdDateTime": "2023-11-09T12:07:43.167Z","id": "12:a12345bc678d901e12345f67890g1234_thread.v2","lastUpdatedDateTime": "2023-11-09T12:07:43.167Z","tenantId": "1a234567-bc8d-9e01-23fg-4h567i8j9k01","webUrl": "https://teams.microsoft.com/l/chat/12%3AA12345BC678D901E12345F67890G1234%40thread.v2/0tenantId=1a234567-bc8d-9e01-23fg-4h567i8j9k01"} diff --git a/plugins/microsoft_teams/requirements.txt b/plugins/microsoft_teams/requirements.txt index 4ef29afbb3..4470fefa18 100755 --- a/plugins/microsoft_teams/requirements.txt +++ b/plugins/microsoft_teams/requirements.txt @@ -3,5 +3,5 @@ # See: https://pip.pypa.io/en/stable/user_guide/#requirements-files maya==0.5.0 beautifulsoup4==4.9.0 -validators==0.17.0 +validators==0.21.0 parameterized==0.8.1 \ No newline at end of file diff --git a/plugins/microsoft_teams/setup.py b/plugins/microsoft_teams/setup.py index 88f215e9ec..c178736d86 100644 --- a/plugins/microsoft_teams/setup.py +++ b/plugins/microsoft_teams/setup.py @@ -3,7 +3,7 @@ setup(name="microsoft_teams-rapid7-plugin", - version="5.1.0", + version="6.0.0", description="The Microsoft Teams plugin allows you to send and trigger workflows on new messages. The plugin will also allow for teams management with the ability to add and remove teams, channels, and users", author="rapid7", author_email="", diff --git a/plugins/microsoft_teams/unit_test/payloads/expected_valid_group_create_teams_chat.json.resp b/plugins/microsoft_teams/unit_test/payloads/expected_valid_group_create_teams_chat.json.resp new file mode 100644 index 0000000000..853450c33f --- /dev/null +++ b/plugins/microsoft_teams/unit_test/payloads/expected_valid_group_create_teams_chat.json.resp @@ -0,0 +1,11 @@ +{ + "chat": { + "@odata.context": "https://graph.microsoft.com/beta/$metadata#chats/$entity", + "chatType": "oneOnOne", + "createdDateTime": "2023-11-09T08:49:51.813Z", + "id": "19:632aea3e-75a7-40fa-bc6b-03794d9c0525_ac785ffe-530a-45a1-bbf4-e275457e464b@thread.v2", + "lastUpdatedDateTime": "2023-11-09T08:49:51.813Z", + "tenantId": "5c824599-dc8c-4d31-96fb-3b886d4f8f10", + "webUrl": "https://teams.microsoft.com/l/chat/19%3A632aea3e-75a7-40fa-bc6b-03794d9c0525_ac785ffe-530a-45a1-bbf4-e275457e464b%40thread.v2/0?tenantId=5c824599-dc8c-4d31-96fb-3b886d4f8f10" + } +} \ No newline at end of file diff --git a/plugins/microsoft_teams/unit_test/payloads/expected_valid_list_messages_in_chat.json.resp b/plugins/microsoft_teams/unit_test/payloads/expected_valid_list_messages_in_chat.json.resp new file mode 100644 index 0000000000..1652e88559 --- /dev/null +++ b/plugins/microsoft_teams/unit_test/payloads/expected_valid_list_messages_in_chat.json.resp @@ -0,0 +1,30 @@ +{ + "messages": [ + { + "attachments": [], + "body": { + "content": "Test message", + "contentType": "text" + }, + "chatId": "19:04e78b73-4a46-4a21-8e0a-05f85df3d76c_ac785ffe-530a-45a1-bbf4-e275457e464b@unq.gbl.spaces", + "createdDateTime": "2023-11-08T10:38:18.048Z", + "etag": "1699439898048", + "from": { + "user": { + "@odata.type": "#microsoft.graph.teamworkUserIdentity", + "displayName": "test user", + "id": "ac785ffe-530a-45a1-bbf4-e275457e464b", + "tenantId": "5c824599-dc8c-4d31-96fb-3b886d4f8f10", + "userIdentityType": "aadUser" + } + }, + "id": "1699439898048", + "importance": "normal", + "lastModifiedDateTime": "2023-11-08T10:38:18.048Z", + "locale": "en-us", + "mentions": [], + "messageType": "message", + "reactions": [] + } + ] +} \ No newline at end of file diff --git a/plugins/microsoft_teams/unit_test/payloads/expected_valid_oneonone_create_teams_chat.json.resp b/plugins/microsoft_teams/unit_test/payloads/expected_valid_oneonone_create_teams_chat.json.resp new file mode 100644 index 0000000000..61265fb7aa --- /dev/null +++ b/plugins/microsoft_teams/unit_test/payloads/expected_valid_oneonone_create_teams_chat.json.resp @@ -0,0 +1,11 @@ +{ + "chat": { + "@odata.context": "https://graph.microsoft.com/beta/$metadata#chats/$entity", + "chatType": "oneOnOne", + "createdDateTime": "2023-11-09T08:49:51.813Z", + "id": "19:632aea3e-75a7-40fa-bc6b-03794d9c0525_ac785ffe-530a-45a1-bbf4-e275457e464b@unq.gbl.spaces", + "lastUpdatedDateTime": "2023-11-09T08:49:51.813Z", + "tenantId": "5c824599-dc8c-4d31-96fb-3b886d4f8f10", + "webUrl": "https://teams.microsoft.com/l/chat/19%3A632aea3e-75a7-40fa-bc6b-03794d9c0525_ac785ffe-530a-45a1-bbf4-e275457e464b%40unq.gbl.spaces/0?tenantId=5c824599-dc8c-4d31-96fb-3b886d4f8f10" + } +} \ No newline at end of file diff --git a/plugins/microsoft_teams/unit_test/payloads/input_invalid_create_teams_chat.json.resp b/plugins/microsoft_teams/unit_test/payloads/input_invalid_create_teams_chat.json.resp new file mode 100644 index 0000000000..1eeaaf4744 --- /dev/null +++ b/plugins/microsoft_teams/unit_test/payloads/input_invalid_create_teams_chat.json.resp @@ -0,0 +1,9 @@ +{ + "members": + [ + { + "user_info": "test@example.com", + "role": "owner" + } + ] +} \ No newline at end of file diff --git a/plugins/microsoft_teams/unit_test/payloads/input_invalid_create_teams_chat_server.json.resp b/plugins/microsoft_teams/unit_test/payloads/input_invalid_create_teams_chat_server.json.resp new file mode 100644 index 0000000000..24d26788de --- /dev/null +++ b/plugins/microsoft_teams/unit_test/payloads/input_invalid_create_teams_chat_server.json.resp @@ -0,0 +1,17 @@ +{ + "members": + [ + { + "user_info": "500 error", + "role": "owner" + }, + { + "user_info": "ac785ffe-530a-45a1-bbf4-e275457e464b", + "role": "owner" + }, + { + "user_info": "ac78ddd5ffe-530a-45a1-bbf4-e275457e464b", + "role": "owner" + } + ] +} \ No newline at end of file diff --git a/plugins/microsoft_teams/unit_test/payloads/input_valid_group_create_teams_chat.json.resp b/plugins/microsoft_teams/unit_test/payloads/input_valid_group_create_teams_chat.json.resp new file mode 100644 index 0000000000..f0505f88c9 --- /dev/null +++ b/plugins/microsoft_teams/unit_test/payloads/input_valid_group_create_teams_chat.json.resp @@ -0,0 +1,17 @@ +{ + "members": + [ + { + "user_info": "test@example.com", + "role": "owner" + }, + { + "user_info": "ac785ffe-530a-45a1-bbf4-e275457e464b", + "role": "owner" + }, + { + "user_info": "dhdjdkjd-530a-45a1-bbf4-e275457e464b", + "role": "owner" + } + ] +} \ No newline at end of file diff --git a/plugins/microsoft_teams/unit_test/payloads/input_valid_oneonone_create_teams_chat.json.resp b/plugins/microsoft_teams/unit_test/payloads/input_valid_oneonone_create_teams_chat.json.resp new file mode 100644 index 0000000000..ce8a095a79 --- /dev/null +++ b/plugins/microsoft_teams/unit_test/payloads/input_valid_oneonone_create_teams_chat.json.resp @@ -0,0 +1,13 @@ +{ + "members": + [ + { + "user_info": "test@example.com", + "role": "owner" + }, + { + "user_info": "ac785ffe-530a-45a1-bbf4-e275457e464b", + "role": "owner" + } + ] +} \ No newline at end of file diff --git a/plugins/microsoft_teams/unit_test/payloads/response_invalid_list_messages_in_chat_bad_json.json.resp b/plugins/microsoft_teams/unit_test/payloads/response_invalid_list_messages_in_chat_bad_json.json.resp new file mode 100644 index 0000000000..3cc762b550 --- /dev/null +++ b/plugins/microsoft_teams/unit_test/payloads/response_invalid_list_messages_in_chat_bad_json.json.resp @@ -0,0 +1 @@ +"" \ No newline at end of file diff --git a/plugins/microsoft_teams/unit_test/payloads/response_valid_group_create_teams_chat.json.resp b/plugins/microsoft_teams/unit_test/payloads/response_valid_group_create_teams_chat.json.resp new file mode 100644 index 0000000000..7e963d0138 --- /dev/null +++ b/plugins/microsoft_teams/unit_test/payloads/response_valid_group_create_teams_chat.json.resp @@ -0,0 +1,9 @@ +{ + "@odata.context": "https://graph.microsoft.com/beta/$metadata#chats/$entity", + "chatType": "oneOnOne", + "createdDateTime": "2023-11-09T08:49:51.813Z", + "id": "19:632aea3e-75a7-40fa-bc6b-03794d9c0525_ac785ffe-530a-45a1-bbf4-e275457e464b@thread.v2", + "lastUpdatedDateTime": "2023-11-09T08:49:51.813Z", + "tenantId": "5c824599-dc8c-4d31-96fb-3b886d4f8f10", + "webUrl": "https://teams.microsoft.com/l/chat/19%3A632aea3e-75a7-40fa-bc6b-03794d9c0525_ac785ffe-530a-45a1-bbf4-e275457e464b%40thread.v2/0?tenantId=5c824599-dc8c-4d31-96fb-3b886d4f8f10" +} \ No newline at end of file diff --git a/plugins/microsoft_teams/unit_test/payloads/response_valid_list_messages_in_chat.json.resp b/plugins/microsoft_teams/unit_test/payloads/response_valid_list_messages_in_chat.json.resp new file mode 100644 index 0000000000..86ded36e1b --- /dev/null +++ b/plugins/microsoft_teams/unit_test/payloads/response_valid_list_messages_in_chat.json.resp @@ -0,0 +1,31 @@ +{ + "value": [ + { + "attachments": [], + "body": { + "content": "Test message", + "contentType": "text" + }, + "chatId": "19:04e78b73-4a46-4a21-8e0a-05f85df3d76c_ac785ffe-530a-45a1-bbf4-e275457e464b@unq.gbl.spaces", + "createdDateTime": "2023-11-08T10:38:18.048Z", + "etag": "1699439898048", + "from": { + "user": { + "@odata.type": "#microsoft.graph.teamworkUserIdentity", + "displayName": "test user", + "id": "ac785ffe-530a-45a1-bbf4-e275457e464b", + "tenantId": "5c824599-dc8c-4d31-96fb-3b886d4f8f10", + "userIdentityType": "aadUser" + } + }, + "id": "1699439898048", + "importance": "normal", + "lastModifiedDateTime": "2023-11-08T10:38:18.048Z", + "locale": "en-us", + "mentions": [], + "messageType": "message", + "reactions": [], + "test_null": null + } + ] +} \ No newline at end of file diff --git a/plugins/microsoft_teams/unit_test/payloads/response_valid_oneonone_create_teams_chat.json.resp b/plugins/microsoft_teams/unit_test/payloads/response_valid_oneonone_create_teams_chat.json.resp new file mode 100644 index 0000000000..931de680a6 --- /dev/null +++ b/plugins/microsoft_teams/unit_test/payloads/response_valid_oneonone_create_teams_chat.json.resp @@ -0,0 +1,9 @@ +{ + "@odata.context": "https://graph.microsoft.com/beta/$metadata#chats/$entity", + "chatType": "oneOnOne", + "createdDateTime": "2023-11-09T08:49:51.813Z", + "id": "19:632aea3e-75a7-40fa-bc6b-03794d9c0525_ac785ffe-530a-45a1-bbf4-e275457e464b@unq.gbl.spaces", + "lastUpdatedDateTime": "2023-11-09T08:49:51.813Z", + "tenantId": "5c824599-dc8c-4d31-96fb-3b886d4f8f10", + "webUrl": "https://teams.microsoft.com/l/chat/19%3A632aea3e-75a7-40fa-bc6b-03794d9c0525_ac785ffe-530a-45a1-bbf4-e275457e464b%40unq.gbl.spaces/0?tenantId=5c824599-dc8c-4d31-96fb-3b886d4f8f10" +} \ No newline at end of file diff --git a/plugins/microsoft_teams/unit_test/test_add_channel_to_team.py b/plugins/microsoft_teams/unit_test/test_add_channel_to_team.py index 249d6e16e9..7d65244f66 100644 --- a/plugins/microsoft_teams/unit_test/test_add_channel_to_team.py +++ b/plugins/microsoft_teams/unit_test/test_add_channel_to_team.py @@ -7,9 +7,10 @@ from unittest.mock import Mock from icon_microsoft_teams.actions.add_channel_to_team.action import AddChannelToTeam -from icon_microsoft_teams.actions.add_channel_to_team.schema import Input +from icon_microsoft_teams.actions.add_channel_to_team.schema import Input, AddChannelToTeamInput, AddChannelToTeamOutput from insightconnect_plugin_runtime.exceptions import PluginException from parameterized import parameterized +from jsonschema import validate from util import Util @@ -32,7 +33,10 @@ def setUp(self) -> None: @parameterized.expand([("Standard",), ("Private",)]) @mock.patch("requests.get", side_effect=Util.mocked_requests) @mock.patch("requests.post", side_effect=Util.mocked_requests) - def test_add_group_owner(self, mock_requests_get: Mock, mock_requests_post: Mock, channel_type: str) -> None: - response = self.action.run({**self.payload, "channel_type": channel_type}) + def test_add_group_owner(self, channel_type: str, mock_requests_post: Mock, mock_requests_get: Mock) -> None: + test_input = {**self.payload, "channel_type": channel_type} + validate(test_input, AddChannelToTeamInput.schema) + response = self.action.run(test_input) expected_response = STUB_EXAMPLE_ACTION_RESPONSE self.assertEqual(response, expected_response) + validate(response, AddChannelToTeamOutput.schema) diff --git a/plugins/microsoft_teams/unit_test/test_add_group_owner.py b/plugins/microsoft_teams/unit_test/test_add_group_owner.py index 67a2b5659b..598ca6e5a3 100644 --- a/plugins/microsoft_teams/unit_test/test_add_group_owner.py +++ b/plugins/microsoft_teams/unit_test/test_add_group_owner.py @@ -6,8 +6,9 @@ from unittest import TestCase, mock from icon_microsoft_teams.actions.add_group_owner.action import AddGroupOwner -from icon_microsoft_teams.actions.add_group_owner.schema import Input +from icon_microsoft_teams.actions.add_group_owner.schema import Input, AddGroupOwnerInput, AddGroupOwnerOutput from insightconnect_plugin_runtime.exceptions import PluginException +from jsonschema import validate from util import Util @@ -26,6 +27,8 @@ def setUp(self) -> None: @mock.patch("requests.get", side_effect=Util.mocked_requests) @mock.patch("requests.post", side_effect=Util.mocked_requests) def test_add_group_owner(self, mock_requests_get, mock_requests_post) -> None: + validate(self.payload, AddGroupOwnerInput.schema) response = self.action.run(self.payload) expected_response = STUB_EXAMPLE_ACTION_RESPONSE self.assertEqual(response, expected_response) + validate(response, AddGroupOwnerOutput.schema) diff --git a/plugins/microsoft_teams/unit_test/test_create_teams_chat.py b/plugins/microsoft_teams/unit_test/test_create_teams_chat.py new file mode 100644 index 0000000000..7aa1a91e97 --- /dev/null +++ b/plugins/microsoft_teams/unit_test/test_create_teams_chat.py @@ -0,0 +1,65 @@ +import sys +import os + +sys.path.append(os.path.abspath("../")) + +from unittest import TestCase, mock +from unittest.mock import Mock +from icon_microsoft_teams.actions.create_teams_chat import CreateTeamsChat +from icon_microsoft_teams.actions.create_teams_chat.schema import CreateTeamsChatInput, CreateTeamsChatOutput +from parameterized import parameterized +from util import Util +from insightconnect_plugin_runtime.exceptions import PluginException +from jsonschema import validate + + +@mock.patch("requests.post", side_effect=Util.mocked_requests) +class TestCreateTeamsChat(TestCase): + @classmethod + def setUp(cls) -> None: + cls.action = Util.default_connector(CreateTeamsChat()) + + @parameterized.expand( + [ + [ + "valid_one_on_one", + Util.load_data("input_valid_oneonone_create_teams_chat"), + Util.load_data("expected_valid_oneonone_create_teams_chat"), + ], + [ + "valid_group", + Util.load_data("input_valid_group_create_teams_chat"), + Util.load_data("expected_valid_group_create_teams_chat"), + ], + ] + ) + def test_create_teams_chat_valid(self, _mock_request: Mock, _test_name: str, input_params: dict, expected: dict): + validate(input_params, CreateTeamsChatInput.schema) + actual = self.action.run(input_params) + self.assertEqual(actual, expected) + validate(actual, CreateTeamsChatOutput.schema) + + @parameterized.expand( + [ + [ + "less than members", + Util.load_data("input_invalid_create_teams_chat"), + "Create chat failed.", + "Less than 2 valid members were provided", + ], + [ + "server error", + Util.load_data("input_invalid_create_teams_chat_server"), + "Create chat failed.", + "Error message", + ], + ] + ) + def test_list_messages_in_chat_invalid( + self, _mock_request: Mock, _test_name: str, input_params: dict, cause: str, assistance: str + ): + validate(input_params, CreateTeamsChatInput.schema) + with self.assertRaises(PluginException) as error: + self.action.run(input_params) + self.assertEqual(error.exception.cause, cause) + self.assertEqual(error.exception.assistance, assistance) diff --git a/plugins/microsoft_teams/unit_test/test_get_message_in_channel.py b/plugins/microsoft_teams/unit_test/test_get_message_in_channel.py index 7ddb9159b1..fa21b7400c 100644 --- a/plugins/microsoft_teams/unit_test/test_get_message_in_channel.py +++ b/plugins/microsoft_teams/unit_test/test_get_message_in_channel.py @@ -7,9 +7,14 @@ from unittest.mock import Mock from icon_microsoft_teams.actions.get_message_in_channel import GetMessageInChannel -from icon_microsoft_teams.actions.get_message_in_channel.schema import Input +from icon_microsoft_teams.actions.get_message_in_channel.schema import ( + Input, + GetMessageInChannelInput, + GetMessageInChannelOutput, +) from util import Util from icon_microsoft_teams.util.komand_clean_with_nulls import remove_null_and_clean +from jsonschema import validate class TestGetMessageInChannel(TestCase): @@ -24,7 +29,8 @@ def setUp(self) -> None: @mock.patch("requests.get", side_effect=Util.mocked_requests) def test_get_message_in_channel(self, mock: Mock): + validate(self.payload, GetMessageInChannelInput.schema) response = self.action.run(self.payload) - expected_response = remove_null_and_clean(Util.load_data("get_message_in_channel")) self.assertEqual(response["message"], expected_response) + validate(response, GetMessageInChannelOutput.schema) diff --git a/plugins/microsoft_teams/unit_test/test_get_message_in_chat.py b/plugins/microsoft_teams/unit_test/test_get_message_in_chat.py index a1843a5113..799cdf2889 100644 --- a/plugins/microsoft_teams/unit_test/test_get_message_in_chat.py +++ b/plugins/microsoft_teams/unit_test/test_get_message_in_chat.py @@ -7,9 +7,10 @@ from unittest.mock import Mock from icon_microsoft_teams.actions.get_message_in_chat import GetMessageInChat -from icon_microsoft_teams.actions.get_message_in_chat.schema import Input +from icon_microsoft_teams.actions.get_message_in_chat.schema import Input, GetMessageInChatInput, GetMessageInChatOutput from util import Util from icon_microsoft_teams.util.komand_clean_with_nulls import remove_null_and_clean +from jsonschema import validate class TestGetMessageInChat(TestCase): @@ -23,7 +24,9 @@ def setUp(self) -> None: @mock.patch("requests.get", side_effect=Util.mocked_requests) def test_get_message_in_chat(self, mock: Mock): + validate(self.payload, GetMessageInChatInput.schema) response = self.action.run(self.payload) expected_response = remove_null_and_clean(Util.load_data("get_message_in_chat")) self.assertEqual(response["message"], expected_response) + validate(response["message"], GetMessageInChatOutput.schema) diff --git a/plugins/microsoft_teams/unit_test/test_get_reply_list.py b/plugins/microsoft_teams/unit_test/test_get_reply_list.py index f41979c75c..0ab72bce78 100644 --- a/plugins/microsoft_teams/unit_test/test_get_reply_list.py +++ b/plugins/microsoft_teams/unit_test/test_get_reply_list.py @@ -8,8 +8,9 @@ from util import Util from icon_microsoft_teams.actions.get_reply_list import GetReplyList -from icon_microsoft_teams.actions.get_reply_list.schema import Input +from icon_microsoft_teams.actions.get_reply_list.schema import Input, GetReplyListInput, GetReplyListOutput from icon_microsoft_teams.util.komand_clean_with_nulls import remove_null_and_clean +from jsonschema import validate class TestGetReplyList(TestCase): @@ -23,7 +24,8 @@ def setUp(self) -> None: @mock.patch("requests.get", side_effect=Util.mocked_requests) def test_get_reply_list(self, mock: Mock): + validate(self.payload, GetReplyListInput.schema) response = self.action.run(self.payload) - expected_response = remove_null_and_clean(Util.load_data("get_reply_list")) self.assertEqual(response["messages"], expected_response.get("value")) + validate(response, GetReplyListOutput.schema) diff --git a/plugins/microsoft_teams/unit_test/test_list_messages_in_chat.py b/plugins/microsoft_teams/unit_test/test_list_messages_in_chat.py new file mode 100644 index 0000000000..7817a4c3c9 --- /dev/null +++ b/plugins/microsoft_teams/unit_test/test_list_messages_in_chat.py @@ -0,0 +1,62 @@ +import sys +import os + +sys.path.append(os.path.abspath("../")) + +from unittest import TestCase, mock +from unittest.mock import Mock +from icon_microsoft_teams.actions.list_messages_in_chat import ListMessagesInChat +from icon_microsoft_teams.actions.list_messages_in_chat.schema import ListMessagesInChatInput, ListMessagesInChatOutput +from parameterized import parameterized +from util import Util +from insightconnect_plugin_runtime.exceptions import PluginException +from jsonschema import validate + + +@mock.patch("requests.get", side_effect=Util.mocked_requests) +class TestListMessagesInChat(TestCase): + @classmethod + def setUp(cls) -> None: + cls.action = Util.default_connector(ListMessagesInChat()) + + @parameterized.expand( + [ + [ + "valid", + {"chat_id": "valid_chat_id"}, + Util.load_data("expected_valid_list_messages_in_chat"), + ] + ] + ) + def test_list_messages_in_chat_valid( + self, _mock_request: Mock, _test_name: str, input_params: dict, expected: dict + ): + validate(input_params, ListMessagesInChatInput.schema) + actual = self.action.run(input_params) + self.assertEqual(actual, expected) + validate(actual, ListMessagesInChatOutput.schema) + + @parameterized.expand( + [ + [ + "bad_request_invalid", + {"chat_id": "invalid_chat_id_bad_rquest"}, + "The server is unable to process the request.", + "Verify your plugin input is correct and not malformed and try again. If the issue persists, please contact support.", + ], + [ + "invalid_json", + {"chat_id": "invalid_chat_id_empty_json"}, + "Received an unexpected response from the server.", + "(non-JSON or no response was received).", + ], + ] + ) + def test_list_messages_in_chat_invalid( + self, _mock_request: Mock, _test_name: str, input_params: dict, cause: str, assistance: str + ): + validate(input_params, ListMessagesInChatInput.schema) + with self.assertRaises(PluginException) as error: + self.action.run(input_params) + self.assertEqual(error.exception.cause, cause) + self.assertEqual(error.exception.assistance, assistance) diff --git a/plugins/microsoft_teams/unit_test/test_new_message_received.py b/plugins/microsoft_teams/unit_test/test_new_message_received.py index 3152786bf0..9eecf2e57b 100644 --- a/plugins/microsoft_teams/unit_test/test_new_message_received.py +++ b/plugins/microsoft_teams/unit_test/test_new_message_received.py @@ -23,6 +23,7 @@ "uuids": [], } + # Get a real payload from file def read_file_to_string(filename): with open(filename) as my_file: diff --git a/plugins/microsoft_teams/unit_test/test_send_message.py b/plugins/microsoft_teams/unit_test/test_send_message.py index 79431081c8..f4bf92b51d 100644 --- a/plugins/microsoft_teams/unit_test/test_send_message.py +++ b/plugins/microsoft_teams/unit_test/test_send_message.py @@ -9,8 +9,9 @@ from insightconnect_plugin_runtime.exceptions import PluginException from icon_microsoft_teams.actions.send_message import SendMessage -from icon_microsoft_teams.actions.send_message.schema import Input +from icon_microsoft_teams.actions.send_message.schema import Input, SendMessageInput, SendMessageOutput from util import Util +from jsonschema import validate @patch("requests.get", side_effect=Util.mocked_requests) @@ -22,16 +23,19 @@ def setUpClass(cls) -> None: @parameterized.expand(Util.load_data("send_message_parameters").get("parameters")) def test_send_message(self, mocked_get, mocked_post, name, team, channel, thread_id, chat_id, message, expected): - actual = self.action.run( - { - Input.TEAM_NAME: team, - Input.CHANNEL_NAME: channel, - Input.THREAD_ID: thread_id, - Input.CHAT_ID: chat_id, - Input.MESSAGE: message, - } - ) + test_input = {Input.MESSAGE: message} + if team: + test_input[Input.TEAM_NAME] = team + if channel: + test_input[Input.CHANNEL_NAME] = channel + if thread_id: + test_input[Input.THREAD_ID] = thread_id + if chat_id: + test_input[Input.CHAT_ID] = chat_id + validate(test_input, SendMessageInput.schema) + actual = self.action.run(test_input) self.assertEqual(actual, expected) + validate(actual, SendMessageOutput.schema) @parameterized.expand( [ @@ -84,15 +88,17 @@ def test_send_message(self, mocked_get, mocked_post, name, team, channel, thread def test_send_message_bad( self, mocked_get, mocked_post, name, team, channel, thread_id, chat_id, message, cause, assistance ): + test_input = {Input.MESSAGE: message} + if team: + test_input[Input.TEAM_NAME] = team + if channel: + test_input[Input.CHANNEL_NAME] = channel + if thread_id: + test_input[Input.THREAD_ID] = thread_id + if chat_id: + test_input[Input.CHAT_ID] = chat_id + validate(test_input, SendMessageInput.schema) with self.assertRaises(PluginException) as e: - self.action.run( - { - Input.TEAM_NAME: team, - Input.CHANNEL_NAME: channel, - Input.THREAD_ID: thread_id, - Input.CHAT_ID: chat_id, - Input.MESSAGE: message, - } - ) + self.action.run(test_input) self.assertEqual(e.exception.cause, cause) self.assertEqual(e.exception.assistance, assistance) diff --git a/plugins/microsoft_teams/unit_test/util.py b/plugins/microsoft_teams/unit_test/util.py index ae3ffa4222..733fa1bb34 100644 --- a/plugins/microsoft_teams/unit_test/util.py +++ b/plugins/microsoft_teams/unit_test/util.py @@ -2,6 +2,7 @@ import logging import os import sys +import requests sys.path.append(os.path.abspath("../")) @@ -53,7 +54,15 @@ def json(self): return Util.load_data(self.filename) def raise_for_status(self): - return + if self.filename in [ + "response_invalid_group_create_teams_chat", + "response_invalid_list_messages_in_chat_bad_json", + ]: + raise ValueError("error") + if self.filename in ["response_invalid_list_messages_in_chat"]: + raise requests.HTTPError() + else: + return if ( args[0] @@ -90,4 +99,20 @@ def raise_for_status(self): return MockResponse("get_message_in_chat", 200) if args[0] == "https://graph.microsoft.com/beta/1/teams/12345/channels/56789/messages/1234567890/replies": return MockResponse("get_reply_list", 200) + if args[0] == "https://graph.microsoft.com/beta/1/chats/valid_chat_id/messages/": + return MockResponse("response_valid_list_messages_in_chat", 200) + if args[0] == "https://graph.microsoft.com/beta/1/chats/invalid_chat_id_bad_rquest/messages/": + return MockResponse("response_invalid_list_messages_in_chat", 400) + if args[0] == "https://graph.microsoft.com/beta/1/chats/invalid_chat_id_empty_json/messages/": + return MockResponse("response_invalid_list_messages_in_chat_bad_json", 200) + + if args[0] == "https://graph.microsoft.com/beta/chats/": + if kwargs.get("json", {}).get("chatType", "") == "oneOnOne": + return MockResponse("response_valid_oneonone_create_teams_chat", 201) + elif kwargs.get("json", {}).get("chatType", "") == "group": + if "500" in kwargs.get("json", {}).get("members", [])[0].get("user@odata.bind", ""): + return MockResponse("response_invalid_group_create_teams_chat", 500) + else: + return MockResponse("response_valid_group_create_teams_chat", 201) + raise Exception("Not implemented")