aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/blueprints/github/__init__.py106
-rw-r--r--app/models.py28
-rw-r--r--app/templates/base.html2
-rw-r--r--app/templates/github/setup_webhook.html23
-rw-r--r--app/templates/packages/view.html9
-rw-r--r--migrations/versions/7a48dbd05780_.py24
6 files changed, 183 insertions, 9 deletions
diff --git a/app/blueprints/github/__init__.py b/app/blueprints/github/__init__.py
index 6bf63a7..d05dffc 100644
--- a/app/blueprints/github/__init__.py
+++ b/app/blueprints/github/__init__.py
@@ -18,19 +18,22 @@ from flask import Blueprint
bp = Blueprint("github", __name__)
-from flask import redirect, url_for, request, flash, abort
-from flask_user import current_user
+from flask import redirect, url_for, request, flash, abort, render_template, jsonify
+from flask_user import current_user, login_required
from sqlalchemy import func
from flask_github import GitHub
from app import github, csrf
from app.models import db, User, APIToken, Package
-from app.utils import loginUser
+from app.utils import loginUser, randomString
from app.blueprints.api.support import error, handleCreateRelease
-import hmac
+import hmac, requests, json
+
+from flask_wtf import FlaskForm
+from wtforms import SelectField, SubmitField
@bp.route("/github/start/")
def start():
- return github.authorize("")
+ return github.authorize("", redirect_uri=url_for("github.callback"))
@bp.route("/github/callback/")
@github.authorized_handler
@@ -40,8 +43,6 @@ def callback(oauth_token):
flash("Authorization failed [err=gh-oauth-login-failed]", "danger")
return redirect(url_for("user.login"))
- import requests
-
# Get Github username
url = "https://api.github.com/user"
r = requests.get(url, headers={"Authorization": "token " + oauth_token})
@@ -121,11 +122,100 @@ def webhook():
if event == "push":
title = json["head_commit"]["message"].partition("\n")[0]
ref = json["after"]
+ elif event == "ping":
+ return jsonify({ "success": True, "message": "Ping successful" })
else:
- return error(400, "Unknown event, expected 'push'")
+ return error(400, "Unsupported event. Only 'push' and 'ping' are supported.")
#
# Perform release
#
return handleCreateRelease(actual_token, package, title, ref)
+
+
+class SetupWebhookForm(FlaskForm):
+ event = SelectField("Event Type", choices=[('push', 'Push'), ('tag', 'New tag')])
+ submit = SubmitField("Save")
+
+
+@bp.route("/github/callback/webhook/")
+@github.authorized_handler
+def callback_webhook(oauth_token=None):
+ pid = request.args.get("pid")
+ if pid is None:
+ abort(404)
+
+ current_user.github_access_token = oauth_token
+ db.session.commit()
+
+ return redirect(url_for("github.setup_webhook", pid=pid))
+
+
+@bp.route("/github/webhook/new/", methods=["GET", "POST"])
+@login_required
+def setup_webhook():
+ pid = request.args.get("pid")
+ if pid is None:
+ abort(404)
+
+ package = Package.query.get(pid)
+ if package is None:
+ abort(404)
+
+ gh_user, gh_repo = package.getGitHubFullName()
+ if gh_user is None or gh_repo is None:
+ flash("Unable to get Github full name from repo address", "danger")
+ return redirect(package.getDetailsURL())
+
+ if current_user.github_access_token is None:
+ return github.authorize("write:repo_hook", \
+ redirect_uri=url_for("github.callback_webhook", pid=pid, _external=True))
+
+ form = SetupWebhookForm(formdata=request.form)
+ if request.method == "POST" and form.validate():
+ token = APIToken()
+ token.name = "Github Webhook for " + package.title
+ token.owner = current_user
+ token.access_token = randomString(32)
+ token.package = package
+
+ event = form.event.data
+ if event != "push" and event != "tag":
+ abort(500)
+
+ # Create webhook
+ url = "https://api.github.com/repos/{}/{}/hooks".format(gh_user, gh_repo)
+ data = {
+ "name": "web",
+ "active": True,
+ "events": [event],
+ "config": {
+ "url": url_for("github.webhook", _external=True),
+ "content_type": "json",
+ "secret": token.access_token
+ },
+ }
+
+ headers = {
+ "Authorization": "token " + current_user.github_access_token
+ }
+
+ r = requests.post(url, headers=headers, data=json.dumps(data))
+ if r.status_code == 201:
+ db.session.add(token)
+ db.session.commit()
+
+ return redirect(package.getDetailsURL())
+ elif r.status_code == 403:
+ current_user.github_access_token = None
+ db.session.commit()
+
+ return github.authorize("write:repo_hook", \
+ redirect_uri=url_for("github.callback_webhook", pid=pid, _external=True))
+ else:
+ flash("Failed to create webhook, received response from Github: " +
+ str(r.json().get("message") or r.status_code), "danger")
+
+ return render_template("github/setup_webhook.html", \
+ form=form, package=package)
diff --git a/app/models.py b/app/models.py
index 5eff2dd..86136f2 100644
--- a/app/models.py
+++ b/app/models.py
@@ -126,6 +126,9 @@ class User(db.Model, UserMixin):
github_username = db.Column(db.String(50, collation="NOCASE"), nullable=True, unique=True)
forums_username = db.Column(db.String(50, collation="NOCASE"), nullable=True, unique=True)
+ # Access token for webhook setup
+ github_access_token = db.Column(db.String(50), nullable=True, server_default=None)
+
# User email information
email = db.Column(db.String(255), nullable=True, unique=True)
email_confirmed_at = db.Column(db.DateTime())
@@ -461,6 +464,31 @@ class Package(db.Model):
def getIsFOSS(self):
return self.license.is_foss and self.media_license.is_foss
+ def getIsOnGitHub(self):
+ if self.repo is None:
+ return False
+
+ url = urlparse(self.repo)
+ return url.netloc == "github.com"
+
+ def getGitHubFullName(self):
+ if self.repo is None:
+ return None
+
+ url = urlparse(self.repo)
+ if url.netloc != "github.com":
+ return None
+
+ import re
+ m = re.search(r"^\/([^\/]+)\/([^\/]+)\/?$", url.path)
+ if m is None:
+ return
+
+ user = m.group(1)
+ repo = m.group(2).replace(".git", "")
+
+ return (user,repo)
+
def getSortedDependencies(self, is_hard=None):
query = self.dependencies
if is_hard is not None:
diff --git a/app/templates/base.html b/app/templates/base.html
index cfe2dfc..4db84d7 100644
--- a/app/templates/base.html
+++ b/app/templates/base.html
@@ -111,7 +111,7 @@
<li class="alert alert-{{category}} container">
<span class="icon_message"></span>
- {{ message|safe }}
+ {{ message }}
<div style="clear: both;"></div>
</li>
diff --git a/app/templates/github/setup_webhook.html b/app/templates/github/setup_webhook.html
new file mode 100644
index 0000000..d0012e2
--- /dev/null
+++ b/app/templates/github/setup_webhook.html
@@ -0,0 +1,23 @@
+{% extends "base.html" %}
+
+{% block title %}
+ {{ _("Setup GitHub webhook") }}
+{% endblock %}
+
+{% from "macros/forms.html" import render_field, render_submit_field, render_radio_field %}
+
+{% block content %}
+ <h1 class="mt-0">{{ self.title() }}</h1>
+
+ <div class="alert alert-info">
+ {{ _("You can delete the webhook at any time by going into Settings > Webhooks on the repository.") }}
+ </div>
+
+ <form method="POST" action="" enctype="multipart/form-data">
+ {{ form.hidden_tag() }}
+
+ {{ render_field(form.event) }}
+
+ {{ render_submit_field(form.submit) }}
+ </form>
+{% endblock %}
diff --git a/app/templates/packages/view.html b/app/templates/packages/view.html
index 77b84da..e5ab1e4 100644
--- a/app/templates/packages/view.html
+++ b/app/templates/packages/view.html
@@ -364,6 +364,15 @@
</ul>
</div>
+ {% if package.getIsOnGitHub() %}
+ <p class="small text-centered">
+ <a href="{{ url_for('github.setup_webhook', pid=package.id) }}">
+ Set up a webhook
+ </a>
+ to create releases automatically.
+ </p>
+ {% endif %}
+
<div class="card my-4">
<div class="card-header">
{% if package.approved and package.checkPerm(current_user, "CREATE_THREAD") %}
diff --git a/migrations/versions/7a48dbd05780_.py b/migrations/versions/7a48dbd05780_.py
new file mode 100644
index 0000000..8f3b96e
--- /dev/null
+++ b/migrations/versions/7a48dbd05780_.py
@@ -0,0 +1,24 @@
+"""empty message
+
+Revision ID: 7a48dbd05780
+Revises: df66c78e6791
+Create Date: 2020-01-24 21:52:49.744404
+
+"""
+from alembic import op
+import sqlalchemy as sa
+from sqlalchemy.dialects import postgresql
+
+# revision identifiers, used by Alembic.
+revision = '7a48dbd05780'
+down_revision = 'df66c78e6791'
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+ op.add_column('user', sa.Column('github_access_token', sa.String(length=50), nullable=True, server_default=None))
+
+
+def downgrade():
+ op.drop_column('user', 'github_access_token')