git_http.py
Python script, ASCII text executable
1import uuid 2from models import * 3from app import app, db, bcrypt 4from misc_utils import * 5from common import git_command 6import os 7import shutil 8import config 9import flask 10import git 11import subprocess 12from flask_httpauth import HTTPBasicAuth 13import zlib 14import re 15import datetime 16 17auth = HTTPBasicAuth(realm=config.AUTH_REALM) 18 19auth_required = flask.Response("Unauthorized Access", 401, 20{"WWW-Authenticate": 'Basic realm="Login Required"'}) 21 22 23@auth.verify_password 24def verify_password(username, password): 25user = User.query.filter_by(username=username).first() 26 27if user and bcrypt.check_password_hash(user.password_hashed, password): 28flask.g.user = username 29return True 30 31return False 32 33 34def get_commit_identity(identity, logged_in_user): 35email = identity.rpartition("<")[2].rpartition(">")[0].strip() 36email_users = db.query(User).filter_by(email=email).all() 37 38 39@app.route("/<username>/<repository>/git-upload-pack", methods=["POST"]) 40@app.route("/git/<username>/<repository>/git-upload-pack", methods=["POST"]) 41@auth.login_required(optional=True) 42def git_upload_pack(username, repository): 43if auth.current_user() is None and not get_visibility(username, repository): 44return auth_required 45if not (get_visibility(username, repository) or get_permission_level(flask.g.user, username, 46repository) is not None): 47flask.abort(403) 48 49server_repo_location = os.path.join(config.REPOS_PATH, username, repository, ".git") 50text = git_command(server_repo_location, flask.request.data, "upload-pack", 51"--stateless-rpc", ".") 52 53return flask.Response(text, content_type="application/x-git-upload-pack-result") 54 55 56@app.route("/<username>/<repository>/git-receive-pack", methods=["POST"]) 57@app.route("/git/<username>/<repository>/git-receive-pack", methods=["POST"]) 58@auth.login_required 59def git_receive_pack(username, repository): 60if not get_permission_level(flask.g.user, username, repository): 61flask.abort(403) 62 63server_repo_location = os.path.join(config.REPOS_PATH, username, repository, ".git") 64text = git_command(server_repo_location, flask.request.data, "receive-pack", 65"--stateless-rpc", ".") 66 67if flask.request.data == b"0000": 68return flask.Response("", content_type="application/x-git-receive-pack-result") 69 70push_info = flask.request.data.split(b"\x00")[0].decode() 71if not push_info: 72return flask.Response(text, content_type="application/x-git-receive-pack-result") 73 74old_sha, new_sha, ref = push_info[4:].split() # discard first 4 characters, used for line length which we don't need 75 76if old_sha == "0" * 40: 77commits_list = subprocess.check_output(["git", "rev-list", new_sha], 78cwd=server_repo_location).decode().strip().split("\n") 79else: 80commits_list = subprocess.check_output(["git", "rev-list", f"{old_sha}..{new_sha}"], 81cwd=server_repo_location).decode().strip().split("\n") 82 83for sha in reversed(commits_list): 84info = git_command(server_repo_location, None, "show", "-s", 85"--format='%H%n%at%n%cn <%ce>%n%B'", sha).decode() 86 87sha, time, identity, body = info.split("\n", 3) 88login = flask.g.user 89 90if not Commit.query.filter_by(identifier=f"/{username}/{repository}/{sha}").first(): 91user = User.query.filter_by(username=login).first() 92repo = Repo.query.filter_by(route=f"/{username}/{repository}").first() 93 94commit = Commit(sha, user, repo, time, body, identity) 95 96db.session.add(commit) 97db.session.commit() 98 99if ref.startswith("refs/heads/"): # if the push is to a branch 100ref = ref.rpartition("/")[2] # get the branch name only 101repo_data = db.session.get(Repo, f"/{username}/{repository}") 102if ref == repo_data.site_branch: 103# Update the site 104from celery_tasks import copy_site 105copy_site.delay(repo_data.route) 106 107return flask.Response(text, content_type="application/x-git-receive-pack-result") 108 109 110@app.route("/<username>/<repository>/info/refs", methods=["GET", "POST"]) 111@app.route("/git/<username>/<repository>/info/refs", methods=["GET", "POST"]) 112@auth.login_required(optional=True) 113def git_info_refs(username, repository): 114server_repo_location = os.path.join(config.REPOS_PATH, username, repository, ".git") 115 116repo = git.Repo(server_repo_location) 117repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 118if not repo_data.default_branch: 119if repo.heads: 120repo_data.default_branch = repo.heads[0].name 121repo.git.checkout("-f", repo_data.default_branch) 122 123if auth.current_user() is None and ( 124not get_visibility(username, repository) or flask.request.args.get( 125"service") == "git-receive-pack"): 126return auth_required 127try: 128if not (get_visibility(username, repository) or get_permission_level(flask.g.user, 129username, 130repository) is not None): 131flask.abort(403) 132except AttributeError: 133return auth_required 134 135service = flask.request.args.get("service") 136 137if service.startswith("git"): 138service = service[4:] 139else: 140flask.abort(403) 141 142if service == "receive-pack": 143try: 144if not get_permission_level(flask.g.user, username, repository): 145flask.abort(403) 146except AttributeError: 147return auth_required 148 149service_line = f"# service=git-{service}\n" 150service_line = (f"{len(service_line) + 4:04x}" + service_line).encode() 151 152if service == "upload-pack": 153text = service_line + b"0000" + git_command(server_repo_location, None, "upload-pack", 154"--stateless-rpc", 155"--advertise-refs", 156"--http-backend-info-refs", ".") 157elif service == "receive-pack": 158refs = git_command(server_repo_location, None, "receive-pack", 159"--http-backend-info-refs", ".") 160text = service_line + b"0000" + refs 161else: 162flask.abort(403) 163 164response = flask.Response(text, content_type=f"application/x-git-{service}-advertisement") 165response.headers["Cache-Control"] = "no-cache" 166 167return response 168