2022-02-08 22:04:25 +01:00
|
|
|
import json
|
2022-02-09 12:00:48 +01:00
|
|
|
import urllib.parse
|
|
|
|
from typing import Union
|
2022-02-08 22:04:25 +01:00
|
|
|
|
|
|
|
import requests
|
2022-02-10 12:34:46 +01:00
|
|
|
from flask import render_template, request, flash, url_for, redirect, g
|
2022-02-08 22:04:25 +01:00
|
|
|
from flask_login import login_required, current_user
|
2022-02-09 12:00:48 +01:00
|
|
|
from werkzeug.datastructures import FileStorage
|
|
|
|
|
2022-02-14 18:02:30 +01:00
|
|
|
from app.config import ZENDESK_HOST, ZENDESK_API_TOKEN
|
2022-02-08 22:04:25 +01:00
|
|
|
from app.dashboard.base import dashboard_bp
|
2022-02-10 12:34:46 +01:00
|
|
|
from app.extensions import limiter
|
2022-02-08 22:04:25 +01:00
|
|
|
from app.log import LOG
|
2022-02-09 12:00:48 +01:00
|
|
|
|
2022-02-10 10:30:28 +01:00
|
|
|
VALID_MIME_TYPES = ["text/plain", "message/rfc822"]
|
2022-02-08 22:04:25 +01:00
|
|
|
|
|
|
|
|
2022-02-09 16:20:55 +01:00
|
|
|
def check_zendesk_response_status(response_code: int) -> bool:
|
|
|
|
if response_code != 201:
|
2022-02-11 13:32:31 +01:00
|
|
|
if response_code in (401, 422):
|
2022-02-14 18:02:30 +01:00
|
|
|
LOG.error("Could not authenticate to Zendesk")
|
2022-02-09 16:20:55 +01:00
|
|
|
else:
|
2022-02-14 18:02:30 +01:00
|
|
|
LOG.error(
|
|
|
|
"Problem with the Zendesk request. Status {}".format(response_code)
|
|
|
|
)
|
2022-02-09 16:20:55 +01:00
|
|
|
return False
|
|
|
|
return True
|
2022-02-09 12:00:48 +01:00
|
|
|
|
|
|
|
|
2022-02-10 12:59:48 +01:00
|
|
|
def upload_file_to_zendesk_and_get_upload_token(
|
|
|
|
email: str, file: FileStorage
|
|
|
|
) -> Union[None, str]:
|
2022-02-10 10:30:28 +01:00
|
|
|
if file.mimetype not in VALID_MIME_TYPES and not file.mimetype.startswith("image/"):
|
|
|
|
flash(
|
|
|
|
"File {} is not an image, text or an email".format(file.filename), "warning"
|
|
|
|
)
|
2022-02-09 16:20:55 +01:00
|
|
|
return
|
2022-02-14 18:02:30 +01:00
|
|
|
|
2022-02-10 10:30:28 +01:00
|
|
|
escaped_filename = urllib.parse.urlencode({"filename": file.filename})
|
|
|
|
url = "https://{}/api/v2/uploads?{}".format(ZENDESK_HOST, escaped_filename)
|
|
|
|
headers = {"content-type": file.mimetype}
|
2022-02-10 12:59:48 +01:00
|
|
|
auth = ("{}/token".format(email), ZENDESK_API_TOKEN)
|
|
|
|
response = requests.post(url, headers=headers, data=file.stream, auth=auth)
|
2022-02-09 16:20:55 +01:00
|
|
|
if not check_zendesk_response_status(response.status_code):
|
|
|
|
return
|
2022-02-14 18:02:30 +01:00
|
|
|
|
2022-02-09 12:00:48 +01:00
|
|
|
data = response.json()
|
2022-02-10 10:30:28 +01:00
|
|
|
return data["upload"]["token"]
|
2022-02-08 22:04:25 +01:00
|
|
|
|
|
|
|
|
2022-02-09 16:41:04 +01:00
|
|
|
def create_zendesk_request(email: str, content: str, files: [FileStorage]) -> bool:
|
2022-02-09 12:00:48 +01:00
|
|
|
tokens = []
|
|
|
|
for file in files:
|
2022-02-09 16:41:04 +01:00
|
|
|
if not file.filename:
|
|
|
|
continue
|
2022-02-10 12:59:48 +01:00
|
|
|
token = upload_file_to_zendesk_and_get_upload_token(email, file)
|
2022-02-09 12:00:48 +01:00
|
|
|
if token is None:
|
|
|
|
return False
|
|
|
|
tokens.append(token)
|
2022-02-14 18:02:30 +01:00
|
|
|
|
2022-02-08 22:04:25 +01:00
|
|
|
data = {
|
2022-02-10 10:30:28 +01:00
|
|
|
"request": {
|
|
|
|
"subject": "Ticket created for user {}".format(current_user.id),
|
|
|
|
"comment": {"type": "Comment", "body": content, "uploads": tokens},
|
|
|
|
"requester": {
|
|
|
|
"name": "SimpleLogin user {}".format(current_user.id),
|
|
|
|
"email": email,
|
2022-02-08 22:04:25 +01:00
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
2022-02-10 10:30:28 +01:00
|
|
|
url = "https://{}/api/v2/requests.json".format(ZENDESK_HOST)
|
|
|
|
headers = {"content-type": "application/json"}
|
2022-02-14 18:02:30 +01:00
|
|
|
auth = (f"{email}/token", ZENDESK_API_TOKEN)
|
2022-02-10 12:59:48 +01:00
|
|
|
response = requests.post(url, data=json.dumps(data), headers=headers, auth=auth)
|
2022-02-09 16:20:55 +01:00
|
|
|
if not check_zendesk_response_status(response.status_code):
|
|
|
|
return False
|
2022-02-14 18:02:30 +01:00
|
|
|
|
2022-02-09 12:00:48 +01:00
|
|
|
return True
|
2022-02-08 22:04:25 +01:00
|
|
|
|
|
|
|
|
2022-02-14 15:58:36 +01:00
|
|
|
@dashboard_bp.route("/support", methods=["GET", "POST"])
|
2022-02-08 22:04:25 +01:00
|
|
|
@login_required
|
2022-02-10 12:34:46 +01:00
|
|
|
@limiter.limit(
|
2022-02-14 15:58:36 +01:00
|
|
|
"2/hour",
|
|
|
|
methods=["POST"],
|
|
|
|
deduct_when=lambda r: hasattr(g, "deduct_limit") and g.deduct_limit,
|
2022-02-10 12:34:46 +01:00
|
|
|
)
|
2022-02-14 18:02:54 +01:00
|
|
|
def support_route():
|
2022-02-09 16:20:55 +01:00
|
|
|
if not ZENDESK_HOST:
|
2022-02-14 18:02:54 +01:00
|
|
|
flash("Support isn't enabled", "error")
|
|
|
|
return redirect(url_for("dashboard.index"))
|
|
|
|
|
|
|
|
if request.method == "POST":
|
|
|
|
content = request.form.get("ticket_content")
|
|
|
|
email = request.form.get("ticket_email")
|
|
|
|
|
|
|
|
if not content:
|
|
|
|
flash("Please add a description", "error")
|
|
|
|
return render_template("dashboard/support.html", ticket_email=email)
|
|
|
|
|
|
|
|
if not email:
|
|
|
|
flash("Please provide an email address", "error")
|
|
|
|
return render_template("dashboard/support.html", ticket_content=content)
|
|
|
|
|
|
|
|
if not create_zendesk_request(
|
|
|
|
email, content, request.files.getlist("ticket_files")
|
|
|
|
):
|
|
|
|
flash(
|
|
|
|
"Cannot create a Zendesk ticket, sorry for the inconvenience! Please retry later.",
|
|
|
|
"error",
|
|
|
|
)
|
|
|
|
return render_template(
|
|
|
|
"dashboard/support.html", ticket_email=email, ticket_content=content
|
|
|
|
)
|
|
|
|
|
|
|
|
# only enable rate limiting for successful Zendesk ticket creation
|
|
|
|
g.deduct_limit = True
|
|
|
|
flash(
|
|
|
|
"Support ticket is created. You will receive an email about its status.",
|
|
|
|
"success",
|
2022-02-10 10:30:28 +01:00
|
|
|
)
|
2022-02-14 18:02:54 +01:00
|
|
|
return redirect(url_for("dashboard.index"))
|
|
|
|
|
|
|
|
return render_template("dashboard/support.html", ticket_email=current_user.email)
|