Skip to content

Commit

Permalink
Merge pull request Semmle#2 from nickfyson/suppression
Browse files Browse the repository at this point in the history
Suppression
  • Loading branch information
aibaars authored Mar 7, 2019
2 parents f5891ce + 17aaa8d commit 6a966af
Show file tree
Hide file tree
Showing 2 changed files with 168 additions and 39 deletions.
5 changes: 4 additions & 1 deletion .env_template
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ FLASK_APP=issues.py
FLASK_DEBUG=0

GIT_REPO_URL=https://github.com/api/v3/repos/USERNAME/REPO/issues
GIT_BOT_USERNAME=LGTM_BOT
GIT_ACCESS_TOKEN=PERSONAL_ACCESS_TOKEN

LGTM_SECRET=SECRET_AS_SPECIFIED_IN_LGTM_INTEGRATION_PANEL
LGTM_WEBHOOK_URL=http://LGTM.EXAMPLE.COM/external-hooks/issue-tracker/INTEGRATION-KEY

SECRET=SECRET_AS_SPECIFIED_IN_LGTM_INTEGRATION_PANEL
202 changes: 164 additions & 38 deletions issues.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,41 @@
import json
import hmac

URL = os.getenv("GIT_REPO_URL")
assert URL != None
LGTM_URL = os.getenv("LGTM_WEBHOOK_URL")
assert LGTM_URL != None

ACCESS_TOKEN = os.getenv("GIT_ACCESS_TOKEN")
assert ACCESS_TOKEN != None
GITHUB_URL = os.getenv("GIT_REPO_URL")
assert GITHUB_URL != None

KEY = os.getenv("LGTM_SECRET", "").encode("utf-8")
GITHUB_BOT_USERNAME = os.getenv("GIT_BOT_USERNAME")
assert GITHUB_BOT_USERNAME != None

GITHUB_TOKEN = os.getenv("GIT_ACCESS_TOKEN")
assert GITHUB_TOKEN != None

KEY = os.getenv("SECRET", "").encode("utf-8")
assert KEY != "".encode("utf-8")

headers = {
"content-type": "application/json",
"Authorization": "Bearer %s" % ACCESS_TOKEN,
}
sess_github = requests.Session()
sess_github.headers.update(
{"content-type": "application/json", "Authorization": "Bearer %s" % GITHUB_TOKEN}
)

sess_lgtm = requests.Session()
sess_lgtm.headers.update({"content-type": "application/json"})

SUPPRESSION_LABEL = "wontfix"

app = Flask(__name__)


@app.route("/", methods=["POST"])
def default():
return jsonify({"message": "success"}), 200


def get_issue_dict(alert, project):
"""Generate payload for creating ticket in GitHub Issues"""

title = "%s (%s)" % (alert["query"]["name"], project["name"])

Expand All @@ -33,56 +50,165 @@ def get_issue_dict(alert, project):
lines.append("> " + "\n> ".join(alert["message"].split("\n")))
lines.append("[View alert on LGTM](%s)" % alert["url"])

return {"title": title, "body": "\n".join(lines), "labels": ["LGTM"]}
labels = ["LGTM"]

if alert.get("suppressed"):
labels.append(SUPPRESSION_LABEL)

return {"title": title, "body": "\n".join(lines), "labels": labels}

@app.route("/", methods=["POST"])
def issues_webhook():

if not app.debug:
def auth_is_valid(signature):
if app.debug:
return True

digest = hmac.new(KEY, request.data, "sha1").hexdigest()
signature = request.headers.get("X-LGTM-Signature", "not-provided")
return hmac.compare_digest(
signature, hmac.new(KEY, request.data, "sha1").hexdigest()
)

if not hmac.compare_digest(signature, digest):
return jsonify({"message": "Unauthorized"}), 401

@app.route("/lgtm", methods=["POST"])
def lgtm_webhook():
"""Handle POST requests coming from LGTM, and pass a translated request to GitHub"""

if not auth_is_valid(request.headers.get("X-LGTM-Signature", "not-provided")):
return jsonify({"code": 403, "error": "Unauthorized"}), 403

json_dict = request.get_json()

transition = json_dict.get("transition")

# we deal with each transition type individually, showing the expected
# behaviour and response codes explicitly

if transition == "create":

data = get_issue_dict(json_dict.get("alert"), json_dict.get("project"))

r = requests.post(URL, data=json.dumps(data), headers=headers)
r = sess_github.post(GITHUB_URL, data=json.dumps(data))

issue_id = r.json()["number"]
issue_id = str(r.json().get("number", None))

else:
if r.ok:
return jsonify({"issue-id": issue_id}), 201
else:
return jsonify({"code": 500, "error": "Internal server error"}), 500

# if not creating a ticket, there should be an issue_id defined
issue_id = json_dict.get("issue-id", None)
if issue_id is None:
return jsonify({"code": 400, "error": "No issue-id provided"}), 400

if transition == "close":

r = sess_github.patch(
GITHUB_URL + "/" + str(issue_id), data=json.dumps({"state": transition})
)
if r.ok:
return jsonify({"issue-id": issue_id}), 200
if r.status_code == 404 and r.json()["message"] == "Not Found":
# if we were trying to close, we don't worry about not finding it
return jsonify({"issue-id": issue_id}), 200
else:
return jsonify({"code": 500, "error": "Internal server error"}), 500

if transition == "reopen":

r = sess_github.patch(
GITHUB_URL + "/" + str(issue_id), data=json.dumps({"state": "open"})
)
if r.ok:
return jsonify({"issue-id": issue_id}), 200
if r.status_code == 404 and r.json()["message"] == "Not Found":
# using 410 we indicate to LGTM that the issue needs to be recreated
return jsonify({"code": 410, "error": "Ticket has been removed."}), 410
else:
return jsonify({"code": 500, "error": "Internal server error"}), 500

if transition == "suppress":

r = sess_github.post(
GITHUB_URL + "/" + str(issue_id) + "/" + "labels",
data=json.dumps([SUPPRESSION_LABEL]),
)
if r.ok:
return jsonify({"issue-id": issue_id}), 200
if r.status_code == 404 and r.json()["message"] == "Not Found":
# if we were trying to suppress, we don't worry about not finding it
return jsonify({"issue-id": issue_id}), 200
else:
return jsonify({"code": 500, "error": "Internal server error"}), 500

if transition == "unsuppress":

r = sess_github.delete(
"/".join([GITHUB_URL, str(issue_id), "labels", SUPPRESSION_LABEL])
)

# if the label was not present on the issue, we don't let this worry us
if not r.ok and r.json().get("message") == "Label does not exist":
r.status_code = 200

if transition not in ["close", "reopen"]:
return (
jsonify({"message": "unknown transition type - %s" % transition}),
400,
if r.ok: # we ensure that the ticket is open in the issue tracker
r = sess_github.patch(
GITHUB_URL + "/" + str(issue_id), data=json.dumps({"state": "open"})
)

issue_id = json_dict.get("issue-id")
if r.ok:
return jsonify({"issue-id": issue_id}), 200
if r.status_code == 404 and r.json()["message"] == "Not Found":
# using 410 we indicate to LGTM that the issue needs to be recreated
return jsonify({"code": 410, "error": "Ticket has been removed."}), 410
else:
return jsonify({"code": 500, "error": "Internal server error"}), 500

if issue_id is None:
return jsonify({"message": "no issue-id provided"}), 400
# when the transition is not recognised, we return a bad request response
return (
jsonify({"code": 400, "error": "unknown transition type - %s" % transition}),
400,
)

# handle a mistmatch between terminology on LGTM and Github
if transition == "reopen":
transition = "open"

r = requests.patch(
os.path.sep.join([URL, str(issue_id)]),
data=json.dumps({"state": transition}),
headers=headers,
)
@app.route("/github", methods=["POST"])
def github_webhook():
"""Handle POST requests coming from GitHub, and pass a translated request to LGTM"""

if not auth_is_valid(
request.headers.get("X-Hub-Signature", "not-provided").split("=")[-1]
):
return jsonify({"code": 403, "error": "Unauthorized"}), 403

json_dict = request.get_json()

action = json_dict.get("action")

if action not in ["labeled", "unlabeled"]:
return jsonify({"status": 200}), 200

if not r.ok:
return r.content, r.status_code
label = json_dict.get("label", {})

return jsonify({"issue-id": issue_id}), r.status_code
if label.get("name", "") != SUPPRESSION_LABEL:
return jsonify({"status": 200}), 200

issue_id = str(json_dict["issue"]["number"])

# When we were responsible for changing the tag, we don't want to pass the webhook back again.
if json_dict["sender"]["login"] == GITHUB_BOT_USERNAME:
return jsonify({"status": 200}), 200

translator = {"labeled": "suppress", "unlabeled": "unsuppress"}

payload = json.dumps({"issue-id": issue_id, "transition": translator[action]})

headers = {
"X-LGTM-Signature": hmac.new(KEY, payload.encode("utf-8"), "sha1").hexdigest()
}

r = sess_lgtm.post(LGTM_URL, data=payload, headers=headers)

if r.ok:
return jsonify({"status": 200}), 200
else:
return app.response_class(
response=r.content, status=r.status_code, mimetype="application/json"
)

0 comments on commit 6a966af

Please sign in to comment.