Skip to content

Commit

Permalink
Merging next to main for release 2.5.1 (#17)
Browse files Browse the repository at this point in the history
* [PAPP-33380] Microsoft Teams: Bugfix - Fixed a bug that caused duplication of 'send message' actions (#16)

* Fixed a bug that caused duplication of 'send message' actions

* Updating min_phantom_version

* Update README.md

* Fixing send_message being executed twice when new access token is generated.

* Kicking off pipeline

* Applying suggestions from PR review

* Applying suggestions from PR review

---------

Co-authored-by: splunk-soar-connectors-admin <admin@splunksoar>

* Bumped up the version of microsoftteams from 2.5.0 to 2.5.1

* Release notes for version 2.5.1

* Release notes for version 2.5.1

---------

Co-authored-by: bbielinski-splunk <[email protected]>
Co-authored-by: splunk-soar-connectors-admin <admin@splunksoar>
Co-authored-by: root <root@splunksoar>
  • Loading branch information
4 people authored Apr 4, 2024
1 parent dba00f8 commit 3a2f154
Show file tree
Hide file tree
Showing 15 changed files with 113 additions and 68 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/phantomcyber/dev-cicd-tools
rev: v1.16
rev: v1.17
hooks:
- id: org-hook
- id: package-app-dependencies
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.

Copyright (c) 2019-2023 Splunk Inc.
Copyright (c) 2019-2024 Splunk Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down
2 changes: 1 addition & 1 deletion NOTICE
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Splunk SOAR Microsoft Teams
Copyright (c) 2019-2023 Splunk Inc.
Copyright (c) 2019-2024 Splunk Inc.

Third-party Software Attributions:

Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@
# Microsoft Teams

Publisher: Splunk
Connector Version: 2.5.0
Connector Version: 2.5.1
Product Vendor: Microsoft
Product Name: Teams
Product Version Supported (regex): ".\*"
Minimum Product Version: 6.0.2
Minimum Product Version: 6.1.1

This app integrates with Microsoft Teams to support various generic and investigative actions

[comment]: # " File: README.md"

[comment]: # " Copyright (c) 2019-2023 Splunk Inc."
[comment]: # " Copyright (c) 2019-2024 Splunk Inc."
[comment]: # ""
[comment]: # "Licensed under the Apache License, Version 2.0 (the 'License');"
[comment]: # "you may not use this file except in compliance with the License."
Expand Down
2 changes: 1 addition & 1 deletion __init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# File: __init__.py
#
# Copyright (c) 2019-2023 Splunk Inc.
# Copyright (c) 2019-2024 Splunk Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down
2 changes: 1 addition & 1 deletion manual_readme_content.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[comment]: # " File: README.md"

[comment]: # " Copyright (c) 2019-2023 Splunk Inc."
[comment]: # " Copyright (c) 2019-2024 Splunk Inc."
[comment]: # ""
[comment]: # "Licensed under the Apache License, Version 2.0 (the 'License');"
[comment]: # "you may not use this file except in compliance with the License."
Expand Down
12 changes: 6 additions & 6 deletions microsoftteams.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@
"product_name": "Teams",
"product_version_regex": ".*",
"publisher": "Splunk",
"license": "Copyright (c) 2019-2023 Splunk Inc.",
"app_version": "2.5.0",
"utctime_updated": "2022-01-14T23:54:39.000000Z",
"license": "Copyright (c) 2019-2024 Splunk Inc.",
"app_version": "2.5.1",
"utctime_updated": "2024-04-04T10:47:00.000000Z",
"package_name": "phantom_microsoftteams",
"main_module": "microsoftteams_connector.py",
"min_phantom_version": "6.0.2",
"min_phantom_version": "6.1.1",
"app_wizard_version": "1.0.0",
"rest_handler": "microsoftteams_connector._handle_rest_request",
"python_version": "3",
Expand Down Expand Up @@ -2143,8 +2143,8 @@
},
{
"module": "soupsieve",
"input_file": "wheels/py3/soupsieve-2.4.1-py3-none-any.whl"
"input_file": "wheels/py3/soupsieve-2.5-py3-none-any.whl"
}
]
}
}
}
141 changes: 91 additions & 50 deletions microsoftteams_connector.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# File: microsoftteams_connector.py
#
# Copyright (c) 2019-2023 Splunk Inc.
# Copyright (c) 2019-2024 Splunk Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand All @@ -21,6 +21,7 @@
import pwd
import sys
import time
from typing import Any, Optional

import encryption_helper
import phantom.app as phantom
Expand Down Expand Up @@ -337,7 +338,7 @@ def _process_empty_response(self, response, action_result):
return RetVal(action_result.set_status(phantom.APP_ERROR, "Status code: {}. Empty response and no information in the header".format(
response.status_code)), None)

def _process_html_response(self, response, action_result):
def _process_html_response(self, response, action_result) -> RetVal[bool, Optional[Any]]:
""" This function is used to process html response.
:param response: response data
Expand Down Expand Up @@ -367,7 +368,7 @@ def _process_html_response(self, response, action_result):

return RetVal(action_result.set_status(phantom.APP_ERROR, message), None)

def _process_json_response(self, response, action_result):
def _process_json_response(self, response, action_result) -> RetVal[bool, Optional[Any]]:
""" This function is used to process json response.
:param response: response data
Expand Down Expand Up @@ -396,7 +397,7 @@ def _process_json_response(self, response, action_result):

return RetVal(action_result.set_status(phantom.APP_ERROR, message), None)

def _process_response(self, response, action_result):
def _process_response(self, response, action_result) -> RetVal[bool, Optional[Any]]:
""" This function is used to process html response.
:param response: response data
Expand Down Expand Up @@ -437,7 +438,15 @@ def _process_response(self, response, action_result):

return RetVal(action_result.set_status(phantom.APP_ERROR, message), None)

def _update_request(self, action_result, endpoint, headers=None, params=None, data=None, method='get'):
def _update_request(
self,
action_result,
endpoint,
headers=None,
params=None,
data=None,
method='get'
) -> tuple[bool, Optional[Any]]:
""" This function is used to update the headers with access_token before making REST call.
:param endpoint: REST endpoint that needs to appended to the service address
Expand Down Expand Up @@ -485,29 +494,57 @@ def _update_request(self, action_result, endpoint, headers=None, params=None, da
'Accept': 'application/json',
'Content-Type': 'application/json'})

ret_val, resp_json = self._make_rest_call(action_result=action_result, endpoint=endpoint, headers=headers,
params=params, data=data, method=method)
status, resp_json = self._make_rest_call(
action_result=action_result,
endpoint=endpoint,
headers=headers,
params=params,
data=data,
method=method
)

message = action_result.get_message().lower()
action_result_message = action_result.get_message().lower()

# If token is expired, generate new token
if "token" in message and "expired" in message:
status = self._generate_new_access_token(action_result=action_result, data=token_data)

if phantom.is_fail(status):
if phantom.is_fail(status):
# If token is expired, generate new token
if self._is_token_expired(action_result_message):
self.debug_print(MSTEAMS_TOKEN_EXPIRED_MSG)
status = self._generate_new_access_token(action_result=action_result, data=token_data)

if phantom.is_fail(status):
return action_result.get_status(), None

headers['Authorization'] = f"Bearer {self._access_token}"

status, resp_json = self._make_rest_call(
action_result=action_result,
endpoint=endpoint,
headers=headers,
params=params,
data=data,
method=method
)

if phantom.is_fail(status):
return action_result.get_status(), None
else:
return action_result.get_status(), None

headers.update({'Authorization': 'Bearer {0}'.format(self._access_token)})

ret_val, resp_json = self._make_rest_call(action_result=action_result, endpoint=endpoint, headers=headers,
params=params, data=data, method=method)

if phantom.is_fail(ret_val):
return action_result.get_status(), None

return phantom.APP_SUCCESS, resp_json

def _make_rest_call(self, endpoint, action_result, headers=None, params=None, data=None, method="get", verify=True):
def _is_token_expired(self, action_result_message: str) -> bool:
return MSTEAMS_TOKEN_EXPIRED_MARKER in action_result_message

def _make_rest_call(
self,
endpoint,
action_result,
headers=None,
params=None,
data=None,
method="get",
verify=True
) -> RetVal[bool, Optional[Any]]:
""" Function that makes the REST call to the app.
:param endpoint: REST endpoint that needs to appended to the service address
Expand Down Expand Up @@ -545,10 +582,10 @@ def _get_asset_name(self, action_result):
asset_id = self.get_asset_id()
rest_endpoint = MSTEAMS_PHANTOM_ASSET_INFO_URL.format(asset_id=asset_id)
url = '{}{}'.format(self.get_phantom_base_url() + 'rest', rest_endpoint)
ret_val, resp_json = self._make_rest_call(action_result=action_result, endpoint=url, verify=False)
status, resp_json = self._make_rest_call(action_result=action_result, endpoint=url, verify=False)

if phantom.is_fail(ret_val):
return ret_val, None
if phantom.is_fail(status):
return status, None

asset_name = resp_json.get('name')
if not asset_name:
Expand All @@ -564,9 +601,9 @@ def _get_phantom_base_url_ms(self, action_result):
base url of phantom
"""
url = '{}{}'.format(self.get_phantom_base_url() + 'rest', MSTEAMS_PHANTOM_SYS_INFO_URL)
ret_val, resp_json = self._make_rest_call(action_result=action_result, endpoint=url, verify=False)
if phantom.is_fail(ret_val):
return ret_val, None
status, resp_json = self._make_rest_call(action_result=action_result, endpoint=url, verify=False)
if phantom.is_fail(status):
return status, None

phantom_base_url = resp_json.get('base_url')
if not phantom_base_url:
Expand Down Expand Up @@ -601,7 +638,7 @@ def _get_app_rest_url(self, action_result):
asset_name)
return phantom.APP_SUCCESS, url_to_app_rest

def _generate_new_access_token(self, action_result, data):
def _generate_new_access_token(self, action_result, data) -> bool:
""" This function is used to generate new access token using the code obtained on authorization.
:param action_result: object of ActionResult class
Expand All @@ -611,9 +648,13 @@ def _generate_new_access_token(self, action_result, data):

req_url = '{}{}'.format(MSTEAMS_LOGIN_BASE_URL, MSTEAMS_SERVER_TOKEN_URL.format(tenant_id=self._tenant))

ret_val, resp_json = self._make_rest_call(action_result=action_result, endpoint=req_url,
data=urllib.urlencode(data), method="post")
if phantom.is_fail(ret_val):
status, resp_json = self._make_rest_call(
action_result=action_result,
endpoint=req_url,
data=urllib.urlencode(data),
method="post"
)
if phantom.is_fail(status):
return action_result.get_status()

self._access_token = resp_json[MSTEAMS_ACCESS_TOKEN_STRING]
Expand Down Expand Up @@ -661,7 +702,7 @@ def _generate_new_access_token(self, action_result, data):
self.debug_print("{}: {}".format(MSTEAMS_DECRYPTION_ERROR, _get_error_message_from_exception(e, self)))
return action_result.set_status(phantom.APP_ERROR, MSTEAMS_DECRYPTION_ERROR)

return phantom.APP_SUCCESS
return action_result.set_status(phantom.APP_SUCCESS, status_message=MSTEAMS_TOKEN_GENERATED_MSG)

def _handle_test_connectivity(self, param):
""" Testing of given credentials and obtaining authorization/admin consent for all other actions.
Expand Down Expand Up @@ -752,9 +793,9 @@ def _handle_test_connectivity(self, param):
self.save_progress(MSTEAMS_CURRENT_USER_INFO_MSG)

url = '{}{}'.format(MSTEAMS_MSGRAPH_API_BASE_URL, MSTEAMS_MSGRAPH_SELF_ENDPOINT)
ret_val, response = self._update_request(action_result=action_result, endpoint=url)
status, response = self._update_request(action_result=action_result, endpoint=url)

if phantom.is_fail(ret_val):
if phantom.is_fail(status):
self.save_progress(MSTEAMS_TEST_CONNECTIVITY_FAILED_MSG)
return action_result.get_status()

Expand Down Expand Up @@ -851,9 +892,9 @@ def _handle_list_users(self, param):
while True:

# make rest call
ret_val, response = self._update_request(endpoint=endpoint, action_result=action_result)
status, response = self._update_request(endpoint=endpoint, action_result=action_result)

if phantom.is_fail(ret_val):
if phantom.is_fail(status):
return action_result.get_status()

for user in response.get('value', []):
Expand All @@ -869,7 +910,7 @@ def _handle_list_users(self, param):

return action_result.set_status(phantom.APP_SUCCESS)

def _verify_parameters(self, group_id, channel_id, action_result):
def _verify_parameters(self, group_id, channel_id, action_result) -> bool:
""" This function is used to verify that the provided group_id is valid and channel_id belongs
to that group_id.
Expand All @@ -884,9 +925,9 @@ def _verify_parameters(self, group_id, channel_id, action_result):

while True:
# make rest call
ret_val, response = self._update_request(endpoint=endpoint, action_result=action_result)
status, response = self._update_request(endpoint=endpoint, action_result=action_result)

if phantom.is_fail(ret_val):
if phantom.is_fail(status):
return action_result.get_status()

for channel in response.get('value', []):
Expand Down Expand Up @@ -935,10 +976,10 @@ def _handle_send_message(self, param):
}

# make rest call
ret_val, response = self._update_request(endpoint=endpoint, action_result=action_result, method='post',
status, response = self._update_request(endpoint=endpoint, action_result=action_result, method='post',
data=json.dumps(data))

if phantom.is_fail(ret_val):
if phantom.is_fail(status):
error_message = action_result.get_message()
if 'teamId' in error_message:
error_message = error_message.replace('teamId', "'group_id'")
Expand All @@ -965,9 +1006,9 @@ def _handle_list_channels(self, param):
while True:

# make rest call
ret_val, response = self._update_request(endpoint=endpoint, action_result=action_result)
status, response = self._update_request(endpoint=endpoint, action_result=action_result)

if phantom.is_fail(ret_val):
if phantom.is_fail(status):
error_message = action_result.get_message()
if 'teamId' in error_message:
error_message = error_message.replace('teamId', "'group_id'")
Expand Down Expand Up @@ -1000,9 +1041,9 @@ def _handle_list_groups(self, param):
while True:

# make rest call using refresh token
ret_val, response = self._update_request(endpoint=endpoint, action_result=action_result)
status, response = self._update_request(endpoint=endpoint, action_result=action_result)

if phantom.is_fail(ret_val):
if phantom.is_fail(status):
return action_result.get_status()

for group in response.get('value', []):
Expand Down Expand Up @@ -1032,9 +1073,9 @@ def _handle_list_teams(self, param):
while True:

# make rest call using refresh token
ret_val, response = self._update_request(endpoint=endpoint, action_result=action_result)
status, response = self._update_request(endpoint=endpoint, action_result=action_result)

if phantom.is_fail(ret_val):
if phantom.is_fail(status):
return action_result.get_status()

for team in response.get('value', []):
Expand Down Expand Up @@ -1110,10 +1151,10 @@ def _handle_create_meeting(self, param):
"attendees": attendees_list
})
# make rest call
ret_val, response = self._update_request(endpoint=endpoint, action_result=action_result, method='post',
status, response = self._update_request(endpoint=endpoint, action_result=action_result, method='post',
data=json.dumps(data))

if phantom.is_fail(ret_val):
if phantom.is_fail(status):
return action_result.get_status()

action_result.add_data(response)
Expand Down
Loading

0 comments on commit 3a2f154

Please sign in to comment.