Skip to content

Commit

Permalink
Zoom ask and mirroring (demisto#29401)
Browse files Browse the repository at this point in the history
* send notification

* send notification

* zoom ask

* zoom async

* zoom async

* zoom readme

* secret token for mirroring

* send-notification command+ mirroring

* unit tests

* fix

* add context cache

* add context cache

* zoom ask tests

* remove unnecessary debug logs

* remove debug logs

* CR comments

* CR comments

* rn

* format

* zoomask tests

* cr

* small fix

* known_words

* Apply suggestions from code review

Co-authored-by: ShirleyDenkberg <[email protected]>

* Update ZoomAsk.yml

* Apply suggestions from code review

Co-authored-by: ShirleyDenkberg <[email protected]>

* zoom basic authentication

* comments

* add certificate to uvicorn to support https

* yml

* readme

---------

Co-authored-by: ShirleyDenkberg <[email protected]>
  • Loading branch information
jbabazadeh and ShirleyDenkberg authored Nov 2, 2023
1 parent 8ca9a50 commit e494994
Show file tree
Hide file tree
Showing 24 changed files with 2,204 additions and 173 deletions.
98 changes: 56 additions & 42 deletions Packs/ApiModules/Scripts/ZoomApiModule/ZoomApiModule.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import demistomock as demisto # noqa: F401
import jwt
from CommonServerPython import * # noqa: F401
from datetime import timedelta
import dateparser
Expand All @@ -20,7 +19,8 @@
INVALID_CREDENTIALS = 'Invalid credentials. Please verify that your credentials are valid.'
INVALID_API_SECRET = 'Invalid API Secret. Please verify that your API Secret is valid.'
INVALID_ID_OR_SECRET = 'Invalid Client ID or Client Secret. Please verify that your ID and Secret is valid.'

INVALID_TOKEN = 'Invalid Authorization token. Please verify that your Bot ID and Bot Secret is valid.'
INVALID_BOT_ID = 'No Chatbot can be found with the given robot_jid value. Please verify that your Bot JID is correct'
'''CLIENT CLASS'''


Expand All @@ -35,39 +35,52 @@ def __init__(
account_id: str | None = None,
client_id: str | None = None,
client_secret: str | None = None,
bot_client_id: str | None = None,
bot_client_secret: str | None = None,
verify=True,
proxy=False,
bot_jid: str | None = None,
):
super().__init__(base_url, verify, proxy)
self.api_key = api_key
self.api_secret = api_secret
self.account_id = account_id
self.client_id = client_id
self.client_secret = client_secret
is_jwt = (api_key and api_secret) and not (client_id and client_secret and account_id)
if is_jwt:
# the user has chosen to use the JWT authentication method (deprecated)
self.access_token: str | None = get_jwt_token(api_key, api_secret) # type: ignore[arg-type]
else:
# the user has chosen to use the OAUTH authentication method.
try:
self.access_token = self.get_oauth_token()
except Exception as e:
demisto.debug(f"Cannot get access token. Error: {e}")
self.access_token = None
self.bot_client_id = bot_client_id
self.bot_client_secret = bot_client_secret
self.bot_jid = bot_jid
try:
self.access_token, self.bot_access_token = self.get_oauth_token()
except Exception as e:
demisto.info(f"Cannot get access token. Error: {e}")
self.access_token = None
self.bot_access_token = None

def generate_oauth_token(self):
"""
Generate an OAuth Access token using the app credentials (AKA: client id and client secret) and the account id
Generate an OAuth Access token using the app credentials (AKA: client id and client secret) and the account id
:return: valid token
"""
:return: valid token
"""
token_res = self._http_request(method="POST", full_url=OAUTH_TOKEN_GENERATOR_URL,
params={"account_id": self.account_id,
"grant_type": "account_credentials"},
auth=(self.client_id, self.client_secret))
return token_res.get('access_token')

def generate_oauth_client_token(self):
"""
Generate an OAuth Access token using the app credentials (AKA: client id and client secret) and the account id
:return: valid token
"""
token_res = self._http_request(method="POST", full_url=OAUTH_TOKEN_GENERATOR_URL,
params={"account_id": self.account_id,
"grant_type": "client_credentials"},
auth=(self.bot_client_id, self.bot_client_secret))
return token_res.get('access_token')

def get_oauth_token(self, force_gen_new_token=False):
"""
Retrieves the token from the server if it's expired and updates the global HEADERS to include it
Expand All @@ -79,10 +92,15 @@ def get_oauth_token(self, force_gen_new_token=False):
"""
now = datetime.now()
ctx = get_integration_context()
client_oauth_token = None
oauth_token = None

if not ctx or not ctx.get('token_info').get('generation_time', force_gen_new_token):
# new token is needed
oauth_token = self.generate_oauth_token()
if self.client_id and self.client_secret:
oauth_token = self.generate_oauth_token()
if self.bot_client_id and self.bot_client_secret:
client_oauth_token = self.generate_oauth_client_token()
ctx = {}
else:
if generation_time := dateparser.parse(
Expand All @@ -93,14 +111,19 @@ def get_oauth_token(self, force_gen_new_token=False):
time_passed = TOKEN_LIFE_TIME
if time_passed < TOKEN_LIFE_TIME:
# token hasn't expired
return ctx.get('token_info').get('oauth_token')
return ctx.get('token_info', {}).get('oauth_token'), ctx.get('token_info', {}).get('client_oauth_token')
else:
# token expired
oauth_token = self.generate_oauth_token()

ctx.update({'token_info': {'oauth_token': oauth_token, 'generation_time': now.strftime("%Y-%m-%dT%H:%M:%S")}})
# new token is needed
if self.client_id and self.client_secret:
oauth_token = self.generate_oauth_token()
if self.bot_client_id and self.bot_client_secret:
client_oauth_token = self.generate_oauth_client_token()

ctx.update({'token_info': {'oauth_token': oauth_token, 'client_oauth_token': client_oauth_token,
'generation_time': now.strftime("%Y-%m-%dT%H:%M:%S")}})
set_integration_context(ctx)
return oauth_token
return oauth_token, client_oauth_token

def error_handled_http_request(self, method, url_suffix='', full_url=None, headers=None,
auth=None, json_data=None, params=None, files=None, data=None,
Expand All @@ -115,30 +138,21 @@ def error_handled_http_request(self, method, url_suffix='', full_url=None, heade
auth=auth, json_data=json_data, params=params, files=files, data=data,
return_empty_response=return_empty_response, resp_type=resp_type, stream=stream)
except DemistoException as e:
if ('Invalid access token' in e.message
or "Access token is expired." in e.message):
self.access_token = self.generate_oauth_token()
headers = {'authorization': f'Bearer {self.access_token}'}
if any(message in e.message for message in ["Invalid access token",
"Access token is expired.",
"Invalid authorization token"]):
if url_suffix == '/im/chat/messages':
demisto.debug('generate new bot client token')
self.bot_access_token = self.generate_oauth_client_token()
headers = {'authorization': f'Bearer {self.bot_access_token}'}
else:
self.access_token = self.generate_oauth_token()
headers = {'authorization': f'Bearer {self.access_token}'}
return super()._http_request(method=method, url_suffix=url_suffix, full_url=full_url, headers=headers,
auth=auth, json_data=json_data, params=params, files=files, data=data,
return_empty_response=return_empty_response, resp_type=resp_type, stream=stream)
else:
raise DemistoException(e.message)
raise DemistoException(e.message, url_suffix)


''' HELPER FUNCTIONS '''


def get_jwt_token(apiKey: str, apiSecret: str) -> str:
"""
Encode the JWT token given the api ket and secret
"""
now = datetime.now()
expire_time = int(now.strftime('%s')) + JWT_LIFETIME
payload = {
'iss': apiKey,

'exp': expire_time
}
encoded = jwt.encode(payload, apiSecret, algorithm='HS256')
return encoded
16 changes: 0 additions & 16 deletions Packs/ApiModules/Scripts/ZoomApiModule/ZoomApiModule_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,19 +144,3 @@ def test_http_request___when_raising_invalid_token_message(mocker):
pass
assert m.call_count == 2
assert generate_token_mock.called


@freeze_time("1988-03-03T11:00:00")
def test_get_jwt_token__encoding_format_check():
"""
Given -
When -
creating a jwt token
Then -
Validate that the token is in the right format
"""
import ZoomApiModule
encoded_token = ZoomApiModule.get_jwt_token(apiKey="blabla", apiSecret="blabla")
# 124 is the expected token length based on parameters given
assert len(encoded_token) == 124
1 change: 1 addition & 0 deletions Packs/Zoom/.pack-ignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ ignore=IM111

[known_words]
zoomapimodule
JWT

[file:Zoom.yml]
ignore=MR108
Expand Down
Loading

0 comments on commit e494994

Please sign in to comment.