Current File : //opt/imunify360/venv/lib64/python3.11/site-packages/defence360agent/utils/support.py |
import asyncio
import io
import json
import socket
import urllib.parse
import urllib.request
from functools import partial
from logging import getLogger
from pathlib import Path
from defence360agent.contracts.config import ANTIVIRUS_MODE
logger = getLogger(__name__)
class ZendeskAPIError(Exception):
def __init__(self, error, description, details):
self.error = error
self.description = description
self.details = details
super().__init__(description)
_API_URL_TMPL = "https://cloudlinux.zendesk.com/api/v2/{}"
_HC_URL_TMPL = "https://cloudlinux.zendesk.com/hc/requests/{}"
# Identifiers for custom fields in API
_PRODUCT_ID = 33267569
_DOCTOR_ID = 43297669
_CLN_ID = 43148369
_PRIVACY_POLICY_ID = 12355021509788
async def send_request(
sender_email,
subject,
description,
doctor_key=None,
cln=None,
attachments=None,
):
"""
Send request to support of Imunify360 via Zendesk API
"""
# Uploading attachments to Zendesk
upload_token = await _upload_attachments(attachments)
# Creating comment object: setting description and attaching
# uploads token
comment = dict(body=description)
if upload_token is not None:
comment["uploads"] = [upload_token]
# Author of request
requester = dict(name=sender_email, email=sender_email)
# Custom fields for support convenience
custom_fields = [
{
"id": _PRODUCT_ID,
"value": "pr_imunify_av" if ANTIVIRUS_MODE else "pr_im360",
},
{"id": _PRIVACY_POLICY_ID, "value": True},
]
if doctor_key:
custom_fields.append({"id": _DOCTOR_ID, "value": doctor_key})
if cln:
custom_fields.append({"id": _CLN_ID, "value": cln})
# Ready request
request = dict(
requester=requester,
subject=subject,
comment=comment,
custom_fields=custom_fields,
)
return await _post_support_request(request)
def _post_data(url, data: bytes, headers, *, params=None, timeout=None):
"""HTTP POST *data* to *url* with given *headers*.
Add query *params* to the *url* if given.
Return (http_status, decoded_json_response) tuple.
"""
if params is not None: # add params to the url
p = urllib.parse.urlparse(url)
query = p.query
if query:
query += "&"
query += urllib.parse.urlencode(params)
url = urllib.parse.urlunparse(
(p.scheme, p.netloc, p.path, p.params, query, p.fragment)
)
def decode_as_json(response):
return json.load(
io.TextIOWrapper(
response,
encoding=response.headers.get_content_charset("utf-8"),
)
)
try:
with urllib.request.urlopen(
urllib.request.Request(url, data=data, headers=headers),
timeout=timeout,
) as response:
return (response.code, decode_as_json(response)) # http status
except socket.timeout:
raise TimeoutError
except OSError as e:
if not hasattr(e, "code"):
raise
# HTTPError
return (e.code, (decode_as_json(e) if e.fp is not None else {}))
async def _post_support_request(request):
"""Return url of the support request or None if request is suspended,
because of we not able to obtain the id of the ticket if it suspended.
"""
url = _API_URL_TMPL.format("requests.json")
headers = {"Content-Type": "application/json"}
data = json.dumps(dict(request=request), sort_keys=True).encode("ascii")
loop = asyncio.get_event_loop()
status, result = await loop.run_in_executor(
None, _post_data, url, data, headers
)
if status == 201:
request_data = result.get("request")
if request_data:
return _HC_URL_TMPL.format(request_data["id"])
elif "suspended_ticket" in result.keys():
return None
else:
raise ZendeskAPIError(
"Response error", "UNKNOWN ERROR", "{!r}".format(result)
)
else:
raise ZendeskAPIError(
result.get("error", "UNKNOWN ERROR"),
result.get("description"),
result.get("details", {}),
)
async def _upload_attachments(attachments):
# Uploading attachments to Zendesk
upload_token = None
if attachments is None:
return upload_token
loop = asyncio.get_event_loop()
for attachment in attachments:
path = Path(attachment)
params = {"filename": path.name}
if upload_token is not None:
params["token"] = upload_token
status, result = await loop.run_in_executor(
None,
partial(
_post_data,
_API_URL_TMPL.format("uploads.json"),
data=path.read_bytes(),
headers={"Content-Type": "application/binary"},
params=params,
),
)
if status != 201:
logger.warning(
"Failed to upload file %s to Zendesk: %s",
attachment,
result["error"],
)
continue
if upload_token is None:
upload_token = result["upload"]["token"]
return upload_token