diff --git a/proxyplugin.py b/proxyplugin.py new file mode 100644 index 0000000..a83722e --- /dev/null +++ b/proxyplugin.py @@ -0,0 +1,222 @@ + +from __future__ import unicode_literals, absolute_import + +import base64 +import urllib.parse +from http.client import HTTPConnection + +import flask +from werkzeug.datastructures import Headers + +import pagure.exceptions +import pagure.lib.query +import pagure.lib.login +import pagure.utils + + +print("proxy loaded") + +PROXYPLUGIN = flask.Blueprint("proxyplugin", __name__) +HOST = None +PORT = None + + +@PROXYPLUGIN.route("/repos/", methods=["GET", "POST"]) +def repos(_): + """ Proxy to git http backend. + """ + + print("repos called") + + env = flask.request.environ + session = flask.g.session + if hasattr(flask.g, "fas_user"): + print("user logged in by cookie") + return forbidden() # disallow cookie authorization + + # read login and password + login = None + password = None + if 'HTTP_AUTHORIZATION' in env: + try: + authtype, credentials = str(env['HTTP_AUTHORIZATION']).split() + assert authtype.lower() == 'basic' + login, password = base64.b64decode(credentials).decode('utf8').split(':') + except Exception: + print("wrong auth format") + return unauthorized() + + # verify password + if login: + user_obj = pagure.lib.query.search_user(session, username = login) + if not user_obj or user_obj.token: + print("wrong user") + return unauthorized() + success = False + try: + success = pagure.lib.login.check_password( + password, + user_obj.password, + seed = pagure.config.config.get("PASSWORD_SEED", None) ) + except pagure.exceptions.PagureException: + pass + if not success: + print("wrong password") + return unauthorized() + + # read path + path = env["REQUEST_URI"].split('?')[0].split('/') + print(path) + if len(path) < 3 or '.' in path or '..' in path: + print("bad path") + return forbidden() + if path.pop(0) != '': + print("no leading slash") + return forbidden() + if path.pop(0) != 'repos': + print("not repos") + return forbidden() + + # read reponame + fork = False + repouser = None + namespace = None + reponame = path.pop(0) + if reponame == 'fork': + if len(path) < 2: + print("bad url format") + return forbidden() + fork = True + repouser = path.pop(0) + reponame = path.pop(0) + if not reponame.endswith('.git'): + if not path: + print("bad url format") + return forbidden() + namespace = reponame + reponame = path.pop(0) + if not reponame.endswith('.git'): + print("bad url format") + return forbidden() + reponame = reponame[:-4] + + # get repo + repo = pagure.lib.query.get_authorized_project( + session = session, + project_name = reponame, + user = repouser, + namespace = namespace, + asuser = login ) + if not repo: + print("no repo [%s][%s][%s][%s]" % (str(fork), repouser, namespace, reponame)) + return forbidden() if login else unauthorized() + + # check access mode + method = env["REQUEST_METHOD"] + getvars = urllib.parse.parse_qs(env["QUERY_STRING"]) + readwrite = env["REQUEST_METHOD"] != 'GET' + if method == 'GET' and getvars.get('service', list()).count('git-receive-pack'): + readwrite = True + elif method == 'POST' and path and path[0] == 'git-upload-pack': + readwrite = False + + # check write access + if readwrite: + print("write [%s]" % str(login)) + if readwrite and (not login or not pagure.utils.is_repo_committer(repo, login, session)): + print("no write access") + return forbidden() if login else unauthorized() + + print("pass to proxy") + return httpproxy(HOST, PORT, login) + + +def badgateway(): + return flask.Response('502 Bad Gateway', status = '502 Bad Gateway') +def forbidden(): + return flask.Response('403 Forbidden', status = '403 Forbidden') +def unauthorized(): + return flask.Response( + '401 Unauthorized', + status = '401 Unauthorized', + headers = Headers([('WWW-Authenticate', 'Basic ream="Authorization area", charset="UTF-8"')]) ) + + +def reader(data, connection = None): + while True: + chunk = data.read(4096) + if not chunk: break + yield chunk + if connection: + connection.close() + print('done') + + +def httpproxy(host, port, login): + assert(host) + assert(port) + + skip_headers = { + 'connection', + 'keep-alive', + 'proxy-authenticate', + 'proxy-authorization', + 'proxy-connection', + 'te', + 'trailers', + 'transfer-encoding', + 'upgrade', + 'authorization' } + + env = flask.request.environ + + # input method + method = env["REQUEST_METHOD"] + + # gather input headers + header_prefix = 'HTTP_' + proxy_headers = dict() + for k, v in env.items(): + if k.startswith(header_prefix): + kk = k[len(header_prefix):].lower().replace('_', '-') + if not kk in skip_headers: + proxy_headers[kk] = v + if 'CONTENT_TYPE' in env: + proxy_headers['content-type'] = env['CONTENT_TYPE'] + try: + length = int(env['CONTENT_LENGTH']) + if length: proxy_headers['content-length'] = str(length) + except Exception: + pass + proxy_headers['host'] = host + if login: + proxy_headers['pagure-user'] = login + print(proxy_headers) + + # input uri + uri = env["REQUEST_URI"] + + # input body + body = reader(env['wsgi.input']) + + connection = None + try: + connection = HTTPConnection(host = host, port = port) + connection.request( + method, + uri, + body = body, + headers = proxy_headers ) + except Exception as e: + return badgateway() + + response = connection.getresponse() + headers = list() + for k, v in response.getheaders(): + if not k.lower() in skip_headers: + headers.append( (k, v) ) + + return flask.Response( + reader(response, connection), + str(response.status) + ' ' + str(response.reason), + Headers(headers) )