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