diff --git a/app/blueprints/api/support.py b/app/blueprints/api/support.py index 2683637..f4e8d9f 100644 --- a/app/blueprints/api/support.py +++ b/app/blueprints/api/support.py @@ -18,7 +18,7 @@ from flask import jsonify, abort, make_response, url_for, current_app from app.logic.packages import do_edit_package -from app.logic.releases import LogicError, do_create_vcs_release, do_create_zip_release +from app.logic.releases import LogicError, do_create_vcs_release, do_create_zip_release, do_handle_webhook_push from app.logic.screenshots import do_create_screenshot, do_order_screenshots from app.models import APIToken, Package, MinetestRelease, PackageScreenshot @@ -53,6 +53,23 @@ def api_create_vcs_release(token: APIToken, package: Package, title: str, ref: s }) +def api_handle_webhook_push(token: APIToken, package: Package, title: str, ref: str, branch: str): + if not token.canOperateOnPackage(package): + error(403, "API token does not have access to the package") + + reason = "Webhook, token=" + token.name + + if branch: + branch = branch.replace("refs/heads/", "") + + task_id = guard(do_handle_webhook_push)(token.owner, package, title, ref, branch, reason) + + return jsonify({ + "success": True, + "task": url_for("tasks.check", id=task_id), + }) + + def api_create_zip_release(token: APIToken, package: Package, title: str, file, min_v: MinetestRelease = None, max_v: MinetestRelease = None, reason="API", commit_hash:str=None): if not token.canOperateOnPackage(package): diff --git a/app/blueprints/github/__init__.py b/app/blueprints/github/__init__.py index 4674170..a7ea36b 100644 --- a/app/blueprints/github/__init__.py +++ b/app/blueprints/github/__init__.py @@ -24,7 +24,7 @@ from sqlalchemy import func, or_, and_ from app import github, csrf from app.models import db, User, APIToken, Package, Permission, AuditSeverity from app.utils import abs_url_for, addAuditLog, login_user_set_active -from app.blueprints.api.support import error, api_create_vcs_release +from app.blueprints.api.support import error, api_create_vcs_release, api_handle_webhook_push import hmac, requests @bp.route("/github/start/") @@ -149,4 +149,7 @@ def webhook(): if package.releases.filter_by(commit_hash=ref).count() > 0: return - return api_create_vcs_release(actual_token, package, title, ref, reason="Webhook") + if event == "push": + return api_handle_webhook_push(actual_token, package, title, ref, json.get("ref")) + else: + return api_create_vcs_release(actual_token, package, title, ref, reason="Webhook") diff --git a/app/blueprints/gitlab/__init__.py b/app/blueprints/gitlab/__init__.py index 990aee6..7e5176c 100644 --- a/app/blueprints/gitlab/__init__.py +++ b/app/blueprints/gitlab/__init__.py @@ -20,7 +20,7 @@ bp = Blueprint("gitlab", __name__) from app import csrf from app.models import Package, APIToken, Permission -from app.blueprints.api.support import error, api_create_vcs_release +from app.blueprints.api.support import error, api_create_vcs_release, api_handle_webhook_push def webhook_impl(): @@ -66,7 +66,10 @@ def webhook_impl(): if package.releases.filter_by(commit_hash=ref).count() > 0: return - return api_create_vcs_release(token, package, title, ref, reason="Webhook") + if event == "push": + return api_handle_webhook_push(token, package, title, ref, json.get("ref")) + else: + return api_create_vcs_release(token, package, title, ref, reason="Webhook") @bp.route("/gitlab/webhook/", methods=["POST"]) diff --git a/app/logic/releases.py b/app/logic/releases.py index 7b1e3c4..fe001e3 100644 --- a/app/logic/releases.py +++ b/app/logic/releases.py @@ -22,7 +22,7 @@ from celery import uuid from app.logic.LogicError import LogicError from app.logic.uploads import upload_file from app.models import PackageRelease, db, Permission, User, Package, MinetestRelease -from app.tasks.importtasks import makeVCSRelease, checkZipRelease +from app.tasks.importtasks import makeVCSRelease, makeVCSReleaseCheckBranch, checkZipRelease from app.utils import AuditSeverity, addAuditLog, nonEmptyOrNone @@ -62,6 +62,14 @@ def do_create_vcs_release(user: User, package: Package, title: str, ref: str, return rel +def do_handle_webhook_push(user: User, package: Package, title: str, commit: str, branch: str, + reason: str = None): + check_can_create_release(user, package) + + task = makeVCSReleaseCheckBranch.delay((user.id, package.id, title, nonEmptyOrNone(commit), branch, reason)) + return task.id + + def do_create_zip_release(user: User, package: Package, title: str, file, min_v: MinetestRelease = None, max_v: MinetestRelease = None, reason: str = None, commit_hash: str = None): diff --git a/app/models/packages.py b/app/models/packages.py index 10c9d28..b03b85c 100644 --- a/app/models/packages.py +++ b/app/models/packages.py @@ -458,7 +458,7 @@ class Package(db.Model): author=self.author.username, name=self.name) else: return url_for("packages.view", - author=self.author.username, name=self.name) + author=self.author.username, name=self.name, _external=False) def getShieldURL(self, type): from app.utils import abs_url_for diff --git a/app/tasks/importtasks.py b/app/tasks/importtasks.py index 29f335f..ea645de 100644 --- a/app/tasks/importtasks.py +++ b/app/tasks/importtasks.py @@ -22,8 +22,8 @@ from kombu import uuid from app.models import * from app.tasks import celery, TaskError -from app.utils import randomString, post_bot_message, addSystemNotification, addSystemAuditLog, get_system_user -from app.utils.git import clone_repo, get_latest_tag, get_latest_commit, get_temp_dir +from app.utils import randomString, post_bot_message, addSystemNotification, addSystemAuditLog, get_system_user, addAuditLog +from app.utils.git import clone_repo, get_latest_tag, get_latest_commit, get_temp_dir, get_default_branch from .minetestcheck import build_tree, MinetestCheckError, ContentType from ..logic.LogicError import LogicError from ..logic.packages import do_edit_package, ALIASES @@ -155,14 +155,14 @@ def checkZipRelease(self, id, path): @celery.task(bind=True) -def makeVCSRelease(self, id, branch): +def makeVCSRelease(self, id, ref): release = PackageRelease.query.get(id) if release is None: raise TaskError("No such release!") elif release.package is None: raise TaskError("No package attached to release") - with clone_repo(release.package.repo, ref=branch, recursive=True) as repo: + with clone_repo(release.package.repo, ref=ref, recursive=True) as repo: postReleaseCheckUpdate(self, release, repo.working_tree_dir) filename = randomString(10) + ".zip" @@ -182,6 +182,38 @@ def makeVCSRelease(self, id, branch): return release.url +@celery.task(bind=True) +def makeVCSReleaseCheckBranch(self, user_id: int, package_id: int, title: str, ref: str, branch: str, reason: str): + package = Package.query.get(package_id) + if package is None: + raise TaskError("No such package!") + + user = User.query.get(user_id) + if user is None: + raise TaskError("No such user!") + + default_branch = get_default_branch(package.repo) + if branch != default_branch: + return + + rel = PackageRelease() + rel.package = package + rel.title = title + rel.url = "" + rel.task_id = uuid() + db.session.add(rel) + + if reason is None: + msg = "Created release {}".format(rel.title) + else: + msg = "Created release {} ({})".format(rel.title, reason) + addAuditLog(AuditSeverity.NORMAL, user, msg, package.getDetailsURL(), package) + + db.session.commit() + + makeVCSRelease(self, rel.id, ref) + + @celery.task() def importRepoScreenshot(id): package = Package.query.get(id) diff --git a/app/utils/git.py b/app/utils/git.py index 7d800f9..d8d6ff6 100644 --- a/app/utils/git.py +++ b/app/utils/git.py @@ -79,6 +79,32 @@ def clone_repo(urlstr, ref=None, recursive=False): .strip()) +def get_default_branch(git_url): + git_url = generateGitURL(git_url) + + g = git.cmd.Git() + + remote_refs = {} + for ref in g.ls_remote(git_url).split('\n'): + hash_ref_list = ref.split('\t') + remote_refs[hash_ref_list[1]] = hash_ref_list[0] + + hash = remote_refs.get("HEAD") + matching = [] + for ref, value in remote_refs.items(): + if value == hash and ref != "HEAD": + matching.append(ref) + + if len(matching) == 1: + return matching[0].replace("refs/heads/", "") + elif len(matching) == 0 or "master" in matching: + return "master" + elif "main" in matching: + return "main" + else: + return matching[0].replace("refs/heads/", "") + + def get_latest_commit(git_url, ref_name=None): git_url = generateGitURL(git_url)