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