Skip to content

Commit

Permalink
Merge pull request #24 from splunk-soar-connectors/next
Browse files Browse the repository at this point in the history
Merging next to main for release 3.4.2
  • Loading branch information
phantom-jacob authored Nov 21, 2024
2 parents 3fc1008 + 6b104b1 commit 87d251f
Show file tree
Hide file tree
Showing 10 changed files with 446 additions and 452 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.17
rev: v1.23
hooks:
- id: org-hook
- id: package-app-dependencies
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
# IMAP

Publisher: Splunk
Connector Version: 3.4.1
Connector Version: 3.4.2
Product Vendor: Generic
Product Name: IMAP
Product Version Supported (regex): ".\*"
Minimum Product Version: 6.1.1
Minimum Product Version: 6.3.0

This app supports email ingestion and various investigative actions over IMAP

Expand Down
4 changes: 2 additions & 2 deletions imap.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
"type": "email",
"publisher": "Splunk",
"main_module": "imap_connector.py",
"app_version": "3.4.1",
"app_version": "3.4.2",
"utctime_updated": "2023-05-10T22:11:21.000000Z",
"package_name": "phantom_imap",
"product_vendor": "Generic",
"product_name": "IMAP",
"product_version_regex": ".*",
"min_phantom_version": "6.1.1",
"min_phantom_version": "6.3.0",
"rest_handler": "request_handler.handle_request",
"fips_compliant": true,
"latest_tested_versions": [
Expand Down
360 changes: 188 additions & 172 deletions imap_connector.py

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion imap_consts.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,10 @@
IMAP_REQUIRED_PARAM_OAUTH = "ERROR: {0} is a required parameter for OAuth Authentication, please specify one."
IMAP_REQUIRED_PARAM_BASIC = "ERROR: {0} is a required parameter for Basic Authentication, please specify one."
IMAP_GENERAL_ERROR_MESSAGE = "{}. Details: {}"
IMAP_STATE_FILE_CORRUPT_ERROR = "Error occurred while loading the state file due to its unexpected format. " \
IMAP_STATE_FILE_CORRUPT_ERROR = (
"Error occurred while loading the state file due to its unexpected format. "
"Resetting the state file with the default format. Please try again."
)
IMAP_ENCRYPTION_ERROR = "Error occurred while encrypting the state file"
IMAP_DECRYPTION_ERROR = "Error occurred while decrypting the state file"

Expand Down
447 changes: 214 additions & 233 deletions process_email.py

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[tool.black]
line-length = 145
target-version = ['py39']
verbose = true

[tool.isort]
line_length = 145
profile = "black"
1 change: 1 addition & 0 deletions release_notes/3.4.2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* Ingestion fix to handle email with non UTF-8 content [PAPP-35075]
63 changes: 26 additions & 37 deletions request_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def handle_request(request, path_parts):


def _get_dir_name_from_app_name(app_name):
app_name = ''.join([x for x in app_name if x.isalnum()])
app_name = "".join([x for x in app_name if x.isalnum()])
app_name = app_name.lower()
if not app_name:
app_name = "app_for_phantom"
Expand All @@ -44,37 +44,34 @@ def __init__(self, request, path_parts):

def _return_error(self, error_msg, status):
state = self._rsh.load_app_state()
state['error'] = True
state["error"] = True
self._rsh.save_app_state(state)
return HttpResponse(error_msg, status=status, content_type="text/plain")

def _get_oauth_token(self, code):
state = self._rsh.load_app_state()
client_id = state['client_id']
redirect_uri = state['redirect_url']
client_secret = base64.b64decode(state['client_secret']).decode()
proxy = state['proxy']
token_url = state['token_url']
client_id = state["client_id"]
redirect_uri = state["redirect_url"]
client_secret = base64.b64decode(state["client_secret"]).decode()
proxy = state["proxy"]
token_url = state["token_url"]

body = {
'grant_type': 'authorization_code',
'redirect_uri': redirect_uri,
'client_id': client_id,
'code': code,
'client_secret': client_secret
"grant_type": "authorization_code",
"redirect_uri": redirect_uri,
"client_id": client_id,
"code": code,
"client_secret": client_secret,
}

try:
r = requests.post(token_url, data=body, proxies=proxy, timeout=DEFAULT_REQUEST_TIMEOUT)
r.raise_for_status()
resp_json = r.json()
except Exception as e:
return False, self._return_error(
"Error retrieving OAuth Token: {}".format(str(e)),
401
)
state['oauth_token'] = resp_json
state['is_encrypted'] = False
return False, self._return_error("Error retrieving OAuth Token: {}".format(str(e)), 401)
state["oauth_token"] = resp_json
state["is_encrypted"] = False
self._rsh.save_app_state(state)

return True, None
Expand All @@ -83,17 +80,17 @@ def handle_request(self):
try:
GET = self._request.GET

asset_id = GET.get('state')
asset_id = GET.get("state")
self._rsh = RequestStateHandler(asset_id)
if not self._rsh.is_valid_asset_id(asset_id):
return self._return_error("Invalid asset id provided", 401)

error = GET.get('error')
error = GET.get("error")
if error:
error_msg = GET.get('error_description')
error_msg = GET.get("error_description")
return self._return_error(error_msg, 401)

code = GET.get('code')
code = GET.get("code")
ret_val, http_object = self._get_oauth_token(code)

if ret_val is False:
Expand All @@ -115,7 +112,7 @@ def _get_state_file(self):

@staticmethod
def is_valid_asset_id(asset_id):
""" This function validates an asset id.
"""This function validates an asset id.
Must be an alphanumeric string of less than 128 characters.
:param asset_id: asset_id
Expand All @@ -139,16 +136,14 @@ def encrypt_state(self, state, connector=None):

try:
if state.get("oauth_token") and state.get("oauth_token", {}).get("access_token"):
state["oauth_token"]["access_token"] = encryption_helper.encrypt(
state["oauth_token"]["access_token"], self._asset_id)
state["oauth_token"]["access_token"] = encryption_helper.encrypt(state["oauth_token"]["access_token"], self._asset_id)
except Exception as ex:
if connector:
connector.error_print("{}: {}".format(IMAP_ENCRYPTION_ERROR, str(ex)))

try:
if state.get("oauth_token") and state.get("oauth_token", {}).get("refresh_token"):
state["oauth_token"]["refresh_token"] = encryption_helper.encrypt(
state["oauth_token"]["refresh_token"], self._asset_id)
state["oauth_token"]["refresh_token"] = encryption_helper.encrypt(state["oauth_token"]["refresh_token"], self._asset_id)
except Exception as ex:
if connector:
connector.error_print("{}: {}".format(IMAP_ENCRYPTION_ERROR, str(ex)))
Expand All @@ -160,21 +155,15 @@ def decrypt_state(self, state, connector=None):
return state
try:
if state.get("oauth_token") and state.get("oauth_token", {}).get("access_token"):
state["oauth_token"]["access_token"] = encryption_helper.decrypt(
state["oauth_token"]["access_token"],
self._asset_id
)
state["oauth_token"]["access_token"] = encryption_helper.decrypt(state["oauth_token"]["access_token"], self._asset_id)
except Exception as ex:
state["oauth_token"]["access_token"] = None
if connector:
connector.error_print("{}: {}".format(IMAP_DECRYPTION_ERROR, str(ex)))

try:
if state.get("oauth_token") and state.get("oauth_token", {}).get("refresh_token"):
state["oauth_token"]["refresh_token"] = encryption_helper.decrypt(
state["oauth_token"]["refresh_token"],
self._asset_id
)
state["oauth_token"]["refresh_token"] = encryption_helper.decrypt(state["oauth_token"]["refresh_token"], self._asset_id)
except Exception as ex:
state["oauth_token"]["refresh_token"] = None
if connector:
Expand All @@ -188,7 +177,7 @@ def save_app_state(self, state, connector=None):
state = self.encrypt_state(state, connector)
state_file = self._get_state_file()
try:
with open(state_file, 'w+') as fp:
with open(state_file, "w+") as fp:
fp.write(json.dumps(state))
except Exception as ex:
if connector:
Expand All @@ -200,7 +189,7 @@ def load_app_state(self, connector=None, decrypt=True):
state_file = self._get_state_file()
state = {}
try:
with open(state_file, 'r') as fp:
with open(state_file, "r") as fp:
in_json = fp.read()
state = json.loads(in_json)
except Exception as ex:
Expand Down
5 changes: 1 addition & 4 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
[flake8]
max-line-length = 145
max-complexity = 28
extend-ignore = F403,E128,E126,E111,E121,E127,E731,E201,E202,F405,E722,D,W292

[isort]
line_length = 145
extend-ignore = F403,E128,E126,E121,E127,E731,E201,E202,E203,E701,F405,E722,D,W503

0 comments on commit 87d251f

Please sign in to comment.