diff options
Diffstat (limited to 'aurweb/git/serve.py')
-rwxr-xr-x | aurweb/git/serve.py | 281 |
1 files changed, 219 insertions, 62 deletions
diff --git a/aurweb/git/serve.py b/aurweb/git/serve.py index 476aea86..44cce75d 100755 --- a/aurweb/git/serve.py +++ b/aurweb/git/serve.py @@ -9,6 +9,7 @@ import time import aurweb.config import aurweb.db +import aurweb.exceptions notify_cmd = aurweb.config.get('notifications', 'notify-cmd') @@ -40,7 +41,7 @@ def list_repos(user): cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", [user]) userid = cur.fetchone()[0] if userid == 0: - die('{:s}: unknown user: {:s}'.format(action, user)) + raise aurweb.exceptions.InvalidUserException(user) cur = conn.execute("SELECT Name, PackagerUID FROM PackageBases " + "WHERE MaintainerUID = ?", [userid]) @@ -51,16 +52,16 @@ def list_repos(user): def create_pkgbase(pkgbase, user): if not re.match(repo_regex, pkgbase): - die('{:s}: invalid repository name: {:s}'.format(action, pkgbase)) + raise aurweb.exceptions.InvalidRepositoryNameException(pkgbase) if pkgbase_exists(pkgbase): - die('{:s}: package base already exists: {:s}'.format(action, pkgbase)) + raise aurweb.exceptions.PackageBaseExistsException(pkgbase) conn = aurweb.db.Connection() cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", [user]) userid = cur.fetchone()[0] if userid == 0: - die('{:s}: unknown user: {:s}'.format(action, user)) + raise aurweb.exceptions.InvalidUserException(user) now = int(time.time()) cur = conn.execute("INSERT INTO PackageBases (Name, SubmittedTS, " + @@ -79,19 +80,19 @@ def create_pkgbase(pkgbase, user): def pkgbase_adopt(pkgbase, user, privileged): pkgbase_id = pkgbase_from_name(pkgbase) if not pkgbase_id: - die('{:s}: package base not found: {:s}'.format(action, pkgbase)) + raise aurweb.exceptions.InvalidPackageBaseException(pkgbase) conn = aurweb.db.Connection() cur = conn.execute("SELECT ID FROM PackageBases WHERE ID = ? AND " + "MaintainerUID IS NULL", [pkgbase_id]) if not privileged and not cur.fetchone(): - die('{:s}: permission denied: {:s}'.format(action, user)) + raise aurweb.exceptions.PermissionDeniedException(user) cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", [user]) userid = cur.fetchone()[0] if userid == 0: - die('{:s}: unknown user: {:s}'.format(action, user)) + raise aurweb.exceptions.InvalidUserException(user) cur = conn.execute("UPDATE PackageBases SET MaintainerUID = ? " + "WHERE ID = ?", [userid, pkgbase_id]) @@ -127,10 +128,10 @@ def pkgbase_get_comaintainers(pkgbase): def pkgbase_set_comaintainers(pkgbase, userlist, user, privileged): pkgbase_id = pkgbase_from_name(pkgbase) if not pkgbase_id: - die('{:s}: package base not found: {:s}'.format(action, pkgbase)) + raise aurweb.exceptions.InvalidPackageBaseException(pkgbase) if not privileged and not pkgbase_has_full_access(pkgbase, user): - die('{:s}: permission denied: {:s}'.format(action, user)) + raise aurweb.exceptions.PermissionDeniedException(user) conn = aurweb.db.Connection() @@ -142,7 +143,7 @@ def pkgbase_set_comaintainers(pkgbase, userlist, user, privileged): [olduser]) userid = cur.fetchone()[0] if userid == 0: - die('{:s}: unknown user: {:s}'.format(action, user)) + raise aurweb.exceptions.InvalidUserException(user) uids_old.add(userid) uids_new = set() @@ -151,7 +152,7 @@ def pkgbase_set_comaintainers(pkgbase, userlist, user, privileged): [newuser]) userid = cur.fetchone()[0] if userid == 0: - die('{:s}: unknown user: {:s}'.format(action, user)) + raise aurweb.exceptions.InvalidUserException(user) uids_new.add(userid) uids_add = uids_new - uids_old @@ -196,10 +197,10 @@ def pkgreq_by_pkgbase(pkgbase_id, reqtype): return [row[0] for row in cur.fetchall()] -def pkgreq_close(reqid, reason, comments, autoclose=False): +def pkgreq_close(reqid, user, reason, comments, autoclose=False): statusmap = {'accepted': 2, 'rejected': 3} if reason not in statusmap: - die('{:s}: invalid reason: {:s}'.format(action, reason)) + raise aurweb.exceptions.InvalidReasonException(reason) status = statusmap[reason] conn = aurweb.db.Connection() @@ -210,7 +211,7 @@ def pkgreq_close(reqid, reason, comments, autoclose=False): cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", [user]) userid = cur.fetchone()[0] if userid == 0: - die('{:s}: unknown user: {:s}'.format(action, user)) + raise aurweb.exceptions.InvalidUserException(user) conn.execute("UPDATE PackageRequests SET Status = ?, ClosureComment = ? " + "WHERE ID = ?", [status, comments, reqid]) @@ -224,18 +225,18 @@ def pkgreq_close(reqid, reason, comments, autoclose=False): def pkgbase_disown(pkgbase, user, privileged): pkgbase_id = pkgbase_from_name(pkgbase) if not pkgbase_id: - die('{:s}: package base not found: {:s}'.format(action, pkgbase)) + raise aurweb.exceptions.InvalidPackageBaseException(pkgbase) initialized_by_owner = pkgbase_has_full_access(pkgbase, user) if not privileged and not initialized_by_owner: - die('{:s}: permission denied: {:s}'.format(action, user)) + raise aurweb.exceptions.PermissionDeniedException(user) # TODO: Support disowning package bases via package request. # Scan through pending orphan requests and close them. comment = 'The user {:s} disowned the package.'.format(user) for reqid in pkgreq_by_pkgbase(pkgbase_id, 'orphan'): - pkgreq_close(reqid, 'accepted', comment, True) + pkgreq_close(reqid, user, 'accepted', comment, True) comaintainers = [] new_maintainer_userid = None @@ -262,17 +263,116 @@ def pkgbase_disown(pkgbase, user, privileged): cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", [user]) userid = cur.fetchone()[0] if userid == 0: - die('{:s}: unknown user: {:s}'.format(action, user)) + raise aurweb.exceptions.InvalidUserException(user) subprocess.Popen((notify_cmd, 'disown', str(pkgbase_id), str(userid))) conn.close() +def pkgbase_flag(pkgbase, user, comment): + pkgbase_id = pkgbase_from_name(pkgbase) + if not pkgbase_id: + raise aurweb.exceptions.InvalidPackageBaseException(pkgbase) + if len(comment) < 3: + raise aurweb.exceptions.InvalidCommentException(comment) + + conn = aurweb.db.Connection() + + cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", [user]) + userid = cur.fetchone()[0] + if userid == 0: + raise aurweb.exceptions.InvalidUserException(user) + + now = int(time.time()) + conn.execute("UPDATE PackageBases SET " + + "OutOfDateTS = ?, FlaggerUID = ?, FlaggerComment = ? " + + "WHERE ID = ? AND OutOfDateTS IS NULL", + [now, userid, comment, pkgbase_id]) + + conn.commit() + + subprocess.Popen((notify_cmd, 'flag', str(userid), str(pkgbase_id))) + + +def pkgbase_unflag(pkgbase, user): + pkgbase_id = pkgbase_from_name(pkgbase) + if not pkgbase_id: + raise aurweb.exceptions.InvalidPackageBaseException(pkgbase) + + conn = aurweb.db.Connection() + + cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", [user]) + userid = cur.fetchone()[0] + if userid == 0: + raise aurweb.exceptions.InvalidUserException(user) + + if user in pkgbase_get_comaintainers(pkgbase): + conn.execute("UPDATE PackageBases SET OutOfDateTS = NULL " + + "WHERE ID = ?", [pkgbase_id]) + else: + conn.execute("UPDATE PackageBases SET OutOfDateTS = NULL " + + "WHERE ID = ? AND (MaintainerUID = ? OR FlaggerUID = ?)", + [pkgbase_id, userid, userid]) + + conn.commit() + + +def pkgbase_vote(pkgbase, user): + pkgbase_id = pkgbase_from_name(pkgbase) + if not pkgbase_id: + raise aurweb.exceptions.InvalidPackageBaseException(pkgbase) + + conn = aurweb.db.Connection() + + cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", [user]) + userid = cur.fetchone()[0] + if userid == 0: + raise aurweb.exceptions.InvalidUserException(user) + + cur = conn.execute("SELECT COUNT(*) FROM PackageVotes " + + "WHERE UsersID = ? AND PackageBaseID = ?", + [userid, pkgbase_id]) + if cur.fetchone()[0] > 0: + raise aurweb.exceptions.AlreadyVotedException(pkgbase) + + now = int(time.time()) + conn.execute("INSERT INTO PackageVotes (UsersID, PackageBaseID, VoteTS) " + + "VALUES (?, ?, ?)", [userid, pkgbase_id, now]) + conn.execute("UPDATE PackageBases SET NumVotes = NumVotes + 1 " + + "WHERE ID = ?", [pkgbase_id]) + conn.commit() + + +def pkgbase_unvote(pkgbase, user): + pkgbase_id = pkgbase_from_name(pkgbase) + if not pkgbase_id: + raise aurweb.exceptions.InvalidPackageBaseException(pkgbase) + + conn = aurweb.db.Connection() + + cur = conn.execute("SELECT ID FROM Users WHERE Username = ?", [user]) + userid = cur.fetchone()[0] + if userid == 0: + raise aurweb.exceptions.InvalidUserException(user) + + cur = conn.execute("SELECT COUNT(*) FROM PackageVotes " + + "WHERE UsersID = ? AND PackageBaseID = ?", + [userid, pkgbase_id]) + if cur.fetchone()[0] == 0: + raise aurweb.exceptions.NotVotedException(pkgbase) + + conn.execute("DELETE FROM PackageVotes WHERE UsersID = ? AND " + + "PackageBaseID = ?", [userid, pkgbase_id]) + conn.execute("UPDATE PackageBases SET NumVotes = NumVotes - 1 " + + "WHERE ID = ?", [pkgbase_id]) + conn.commit() + + def pkgbase_set_keywords(pkgbase, keywords): pkgbase_id = pkgbase_from_name(pkgbase) if not pkgbase_id: - die('{:s}: package base not found: {:s}'.format(action, pkgbase)) + raise aurweb.exceptions.InvalidPackageBaseException(pkgbase) conn = aurweb.db.Connection() @@ -310,6 +410,26 @@ def pkgbase_has_full_access(pkgbase, user): return cur.fetchone()[0] > 0 +def log_ssh_login(user, remote_addr): + conn = aurweb.db.Connection() + + now = int(time.time()) + conn.execute("UPDATE Users SET LastSSHLogin = ?, " + + "LastSSHLoginIPAddress = ? WHERE Username = ?", + [now, remote_addr, user]) + + conn.commit() + conn.close() + + +def bans_match(remote_addr): + conn = aurweb.db.Connection() + + cur = conn.execute("SELECT COUNT(*) FROM Bans WHERE IPAddress = ?", + [remote_addr]) + return cur.fetchone()[0] > 0 + + def die(msg): sys.stderr.write("{:s}\n".format(msg)) exit(1) @@ -331,29 +451,36 @@ def usage(cmds): exit(0) -def main(): - user = os.environ.get('AUR_USER') - privileged = (os.environ.get('AUR_PRIVILEGED', '0') == '1') - ssh_cmd = os.environ.get('SSH_ORIGINAL_COMMAND') - ssh_client = os.environ.get('SSH_CLIENT') +def checkarg_atleast(cmdargv, *argdesc): + if len(cmdargv) - 1 < len(argdesc): + msg = 'missing {:s}'.format(argdesc[len(cmdargv) - 1]) + raise aurweb.exceptions.InvalidArgumentsException(msg) - if not ssh_cmd: - die_with_help("Interactive shell is disabled.") - cmdargv = shlex.split(ssh_cmd) - action = cmdargv[0] - remote_addr = ssh_client.split(' ')[0] if ssh_client else None +def checkarg_atmost(cmdargv, *argdesc): + if len(cmdargv) - 1 > len(argdesc): + raise aurweb.exceptions.InvalidArgumentsException('too many arguments') + + +def checkarg(cmdargv, *argdesc): + checkarg_atleast(cmdargv, *argdesc) + checkarg_atmost(cmdargv, *argdesc) + + +def serve(action, cmdargv, user, privileged, remote_addr): if enable_maintenance: if remote_addr not in maintenance_exc: - die("The AUR is down due to maintenance. We will be back soon.") + raise aurweb.exceptions.MaintenanceException + if bans_match(remote_addr): + raise aurweb.exceptions.BannedException + log_ssh_login(user, remote_addr) if action == 'git' and cmdargv[1] in ('upload-pack', 'receive-pack'): action = action + '-' + cmdargv[1] del cmdargv[1] if action == 'git-upload-pack' or action == 'git-receive-pack': - if len(cmdargv) < 2: - die_with_help("{:s}: missing path".format(action)) + checkarg(cmdargv, 'path') path = cmdargv[1].rstrip('/') if not path.startswith('/'): @@ -362,11 +489,11 @@ def main(): path = path + '.git' pkgbase = path[1:-4] if not re.match(repo_regex, pkgbase): - die('{:s}: invalid repository name: {:s}'.format(action, pkgbase)) + raise aurweb.exceptions.InvalidRepositoryNameException(pkgbase) if action == 'git-receive-pack' and pkgbase_exists(pkgbase): if not privileged and not pkgbase_has_write_access(pkgbase, user): - die('{:s}: permission denied: {:s}'.format(action, user)) + raise aurweb.exceptions.PermissionDeniedException(user) os.environ["AUR_USER"] = user os.environ["AUR_PKGBASE"] = pkgbase @@ -374,57 +501,58 @@ def main(): cmd = action + " '" + repo_path + "'" os.execl(git_shell_cmd, git_shell_cmd, '-c', cmd) elif action == 'set-keywords': - if len(cmdargv) < 2: - die_with_help("{:s}: missing repository name".format(action)) + checkarg(cmdargv, 'repository name') pkgbase_set_keywords(cmdargv[1], cmdargv[2:]) elif action == 'list-repos': - if len(cmdargv) > 1: - die_with_help("{:s}: too many arguments".format(action)) + checkarg(cmdargv) list_repos(user) elif action == 'setup-repo': - if len(cmdargv) < 2: - die_with_help("{:s}: missing repository name".format(action)) - if len(cmdargv) > 2: - die_with_help("{:s}: too many arguments".format(action)) + checkarg(cmdargv, 'repository name') warn('{:s} is deprecated. ' 'Use `git push` to create new repositories.'.format(action)) create_pkgbase(cmdargv[1], user) elif action == 'restore': - if len(cmdargv) < 2: - die_with_help("{:s}: missing repository name".format(action)) - if len(cmdargv) > 2: - die_with_help("{:s}: too many arguments".format(action)) + checkarg(cmdargv, 'repository name') pkgbase = cmdargv[1] - if not re.match(repo_regex, pkgbase): - die('{:s}: invalid repository name: {:s}'.format(action, pkgbase)) - - if pkgbase_exists(pkgbase): - die('{:s}: package base exists: {:s}'.format(action, pkgbase)) create_pkgbase(pkgbase, user) os.environ["AUR_USER"] = user os.environ["AUR_PKGBASE"] = pkgbase os.execl(git_update_cmd, git_update_cmd, 'restore') elif action == 'adopt': - if len(cmdargv) < 2: - die_with_help("{:s}: missing repository name".format(action)) - if len(cmdargv) > 2: - die_with_help("{:s}: too many arguments".format(action)) + checkarg(cmdargv, 'repository name') pkgbase = cmdargv[1] pkgbase_adopt(pkgbase, user, privileged) elif action == 'disown': - if len(cmdargv) < 2: - die_with_help("{:s}: missing repository name".format(action)) - if len(cmdargv) > 2: - die_with_help("{:s}: too many arguments".format(action)) + checkarg(cmdargv, 'repository name') pkgbase = cmdargv[1] pkgbase_disown(pkgbase, user, privileged) + elif action == 'flag': + checkarg(cmdargv, 'repository name', 'comment') + + pkgbase = cmdargv[1] + comment = cmdargv[2] + pkgbase_flag(pkgbase, user, comment) + elif action == 'unflag': + checkarg(cmdargv, 'repository name') + + pkgbase = cmdargv[1] + pkgbase_unflag(pkgbase, user) + elif action == 'vote': + checkarg(cmdargv, 'repository name') + + pkgbase = cmdargv[1] + pkgbase_vote(pkgbase, user) + elif action == 'unvote': + checkarg(cmdargv, 'repository name') + + pkgbase = cmdargv[1] + pkgbase_unvote(pkgbase, user) elif action == 'set-comaintainers': - if len(cmdargv) < 2: - die_with_help("{:s}: missing repository name".format(action)) + checkarg_atleast(cmdargv, 'repository name') pkgbase = cmdargv[1] userlist = cmdargv[2:] @@ -433,18 +561,47 @@ def main(): cmds = { "adopt <name>": "Adopt a package base.", "disown <name>": "Disown a package base.", + "flag <name> <comment>": "Flag a package base out-of-date.", "help": "Show this help message and exit.", "list-repos": "List all your repositories.", "restore <name>": "Restore a deleted package base.", "set-comaintainers <name> [...]": "Set package base co-maintainers.", "set-keywords <name> [...]": "Change package base keywords.", "setup-repo <name>": "Create a repository (deprecated).", + "unflag <name>": "Remove out-of-date flag from a package base.", + "unvote <name>": "Remove vote from a package base.", + "vote <name>": "Vote for a package base.", "git-receive-pack": "Internal command used with Git.", "git-upload-pack": "Internal command used with Git.", } usage(cmds) else: - die_with_help("invalid command: {:s}".format(action)) + msg = 'invalid command: {:s}'.format(action) + raise aurweb.exceptions.InvalidArgumentsException(msg) + + +def main(): + user = os.environ.get('AUR_USER') + privileged = (os.environ.get('AUR_PRIVILEGED', '0') == '1') + ssh_cmd = os.environ.get('SSH_ORIGINAL_COMMAND') + ssh_client = os.environ.get('SSH_CLIENT') + + if not ssh_cmd: + die_with_help("Interactive shell is disabled.") + cmdargv = shlex.split(ssh_cmd) + action = cmdargv[0] + remote_addr = ssh_client.split(' ')[0] if ssh_client else None + + try: + serve(action, cmdargv, user, privileged, remote_addr) + except aurweb.exceptions.MaintenanceException: + die("The AUR is down due to maintenance. We will be back soon.") + except aurweb.exceptions.BannedException: + die("The SSH interface is disabled for your IP address.") + except aurweb.exceptions.InvalidArgumentsException as e: + die_with_help('{:s}: {}'.format(action, e)) + except aurweb.exceptions.AurwebException as e: + die('{:s}: {}'.format(action, e)) if __name__ == '__main__': |