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